Type classes power JSON codecs, ordering, numeric abstraction and every major functional library.
Scala 3 contextual abstractions let the compiler pass arguments for you based on their type.
- A
givendefines a canonical instance of a type:given Ordering[Version] = Ordering.by(v => (v.major, v.minor)). - A
usingclause declares a parameter the compiler fills in from givens in scope:def sortAll[A](xs: List[A])(using ord: Ordering[A]). Callers writesortAll(versions), or pass one explicitly withsortAll(versions)(using reverseOrd). - A context bound is shorthand:
def sortAll[A: Ordering](xs: List[A]), andsummon[Ordering[A]]retrieves the instance.
Together these implement type classes: a trait describing a capability (trait Show[A] { def show(a: A): String }), given instances for each type, and generic functions requiring the capability. Unlike inheritance, you can add a capability to types you don't own (Int, java.time.Instant) and choose different instances in different scopes.
Where does the compiler look? In the lexical scope (including imports) and in the implicit scope of the involved types: companion objects of Show, of A, and of their parts. Put instances for your own types in their companions and they're found without imports. Importing givens needs the special form import mylib.given.
derives Show on a case class auto-generates an instance when the type class supports derivation.
Example
trait Show[A]:
def show(a: A): String
object Show:
given Show[Int] with
def show(n: Int) = s"#$n"
given [A](using s: Show[A]): Show[List[A]] with
def show(xs: List[A]) = xs.map(s.show).mkString("[", ", ", "]")
case class Version(major: Int, minor: Int)
object Version:
given Ordering[Version] = Ordering.by(v => (v.major, v.minor))
given Show[Version] = v => s"v${v.major}.${v.minor}"
def describe[A: Show](a: A): String = "value " + summon[Show[A]].show(a)
@main def tc(): Unit =
println(describe(42)) // value #42
println(describe(List(1, 2))) // value [#1, #2]
val vs = List(Version(1, 10), Version(1, 2), Version(0, 9))
println(vs.sorted.map(describe(_))) // List(value v0.9, value v1.2, value v1.10)
println(vs.max) // Version(1,10)
val newestFirst = summon[Ordering[Version]].reverse
println(vs.sorted(using newestFirst).head) // Version(1,10)Remember
givenprovides an instance;usingrequires one- Context bounds
[A: Show]plussummonare shorthand - Type classes add capabilities to types you don't own
- Companion objects are searched automatically; others need
import x.given
Watch out for
- Defining givens in an unrelated object and forgetting
import thatObject.given; a normal wildcard import doesn't bring givens.