Extension Methods
Prerequisites: Generics
Extension methods add methods to existing types — even ones you
didn't write, like String or Int — without changing or subclassing
them.
extension (s: String)
def isPalindrome: Boolean =
val cleaned = s.toLowerCase.filter(_.isLetterOrDigit)
cleaned == cleaned.reverse
def words: List[String] = s.split("\\s+").filter(_.nonEmpty).toList
extension (n: Int)
def isEven: Boolean = n % 2 == 0
def times(action: => Unit): Unit = for _ <- 1 to n do action
@main def extensions(): Unit =
println("A man, a plan, a canal: Panama".isPalindrome)
println(" hello scala world ".words)
println(10.isEven)
3.times(print("hi "))
println()
true
List(hello, scala, world)
true
hi hi hi
The methods are called like normal methods, but they're ordinary functions underneath — they can't access private members.
Generic extensions
extension [A](items: List[A])
def second: Option[A] = items.drop(1).headOption
def frequencies: Map[A, Int] = items.groupMapReduce(identity)(_ => 1)(_ + _)
@main def genericExtensions(): Unit =
println(List(1, 2, 3).second)
println(List("x").second)
println(List("a", "b", "a").frequencies)
Some(2)
None
Map(a -> 2, b -> 1)
Where to put them
Define extensions in an object and import them where needed
(import StringExtensions.*), so it's clear where a method comes from.
They're also central to type classes in Level 3.
(Scala 2 used implicit class for the same purpose — you'll see it in
older code.)