Skip to content
AgileLogics
4 min read

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.)