Extension methods

Scala · Advanced · 10 min

Extensions give readable APIs on types you don't control without wrappers or inheritance.

An extension method adds a method to a type after the fact:

extension (s: String) def words: List[String] = s.split("\\s+").toList

Now "a b c".words works anywhere the extension is in scope. Several methods can share one clause with an indented block, type parameters are allowed (extension [A](xs: List[A]) def second: Option[A]), and operators can be defined too.

The compiler finds an extension when it's visible by name (defined or imported in scope) or when it's defined in a given instance or companion object in the implicit scope of the receiver's type. This makes extensions a natural partner for type classes: define them inside the type class trait and every type with an instance gets the syntax:

trait Show[A]:
  extension (a: A) def show: String

Under the hood an extension is just a method with the receiver as an extra first parameter; there's no wrapper allocation, so it's free at runtime.

Extensions can't override existing methods: if the type already has a member with that name, the real member wins. Use them for convenience and domain-specific syntax, and keep them discoverable by putting them next to the types or type classes they extend.

Example

extension (s: String)
  def words: List[String] = s.trim.split("\\s+").toList
  def isPalindrome: Boolean =
    val c = s.toLowerCase.filter(_.isLetter)
    c == c.reverse

extension [A](xs: List[A])
  def second: Option[A] = xs.drop(1).headOption

case class Money(cents: Long)
extension (m: Money)
  def +(o: Money): Money = Money(m.cents + o.cents)
  def pretty: String = f"$$${m.cents / 100.0}%.2f"

trait Show[A]:
  extension (a: A) def show: String
given Show[Boolean] with
  extension (b: Boolean) def show = if b then "yes" else "no"

@main def ext(): Unit =
  println("  to be  or not ".words)           // List(to, be, or, not)
  println("Never odd or even".isPalindrome)   // true
  println(List(1, 2, 3).second)               // Some(2)
  println((Money(150) + Money(275)).pretty)   // $4.25
  println(true.show)                          // yes

Remember

  • extension (x: T) def m = ... adds methods to existing types
  • Extensions inside type classes give syntax to every instance
  • No runtime wrapper: the receiver is just the first parameter
  • Existing members always take precedence

Watch out for

  • Defining an extension with the same name as an existing method and wondering why it's never called.

Check yourself

  1. String already has a method trim. You define extension (s: String) def trim = "x". What does " a ".trim return?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.scala-lang.org/scala3/book/ca-extension-methods.html

Builds on

Unlocks

Nothing builds on this yet.