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+").toListNow "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: StringUnder 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) // yesRemember
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.