Lesson 14 / 25
Givens, Using Clauses and Type Classes
Use contextual abstractions to pass context and implement type classes.
Contextual abstractions in Scala 3
Scala 3 replaces Scala 2's overloaded implicit keyword with clearer constructs. A given instance defines a canonical value for a type, and a using clause declares that a method needs such a value from the context: def sorted(using ord: Ordering[A]). The compiler finds the given automatically at the call site, though you can still pass one explicitly with using. This is how Scala implements type classes: a trait such as trait Show[A] { def show(a: A): String } describes a capability, givens provide instances for specific types (given Show[Money]), and generic code requires the capability with [A: Show] (a context bound, shorthand for a using parameter). Type classes add behaviour to types you do not own, such as JSON encoding, ordering, equality and serialisation, without inheritance. derives clauses (case class Item(...) derives Codec) ask a library to generate instances automatically. Givens are imported explicitly with import x.given, so it stays clear where they come from. Use them for genuine context (an execution context, a configuration, a type class), not to hide ordinary parameters.
A Show type class with givens
Instances for built-in and domain types, and a generic function needing one.
trait Show[A]:
extension (a: A) def show: String
given Show[Int] with
extension (n: Int) def show: String = n.toString
case class Money(paise: Long)
given Show[Money] with
extension (m: Money) def show: String = f"Rs ${m.paise / 100.0}%.2f"
given [A](using Show[A]): Show[List[A]] with // derived instance for lists
extension (xs: List[A]) def show: String = xs.map(_.show).mkString("[", ", ", "]")
def printAll[A: Show](items: A*): Unit = // context bound = using Show[A]
items.foreach(i => println(i.show))
printAll(Money(4950), Money(19900)) // Rs 49.50, Rs 199.00
println(List(Money(100), Money(250)).show) // [Rs 1.00, Rs 2.50]
case class Gadget(name: String, pricePaise: Long)
given Ordering[Gadget] = Ordering.by[Gadget, Long](_.pricePaise)
val cheapestFirst = List(Gadget("ink", 19900), Gadget("pen", 4950)).sorted // uses the given OrderingA plug adapter in the hotel room
A using clause is a device that needs a socket adapter. Instead of carrying one in every call, the hotel room (the scope) provides a given adapter, and the compiler plugs it in for you.
Quick check: What does a context bound such as [A: Show] mean?
- A must extend Show
- A is optional
- Show is a subclass of A
- The method needs a given Show[A] instance from the context
Answer
The method needs a given Show[A] instance from the context — Context bounds are shorthand for a using parameter of type Show[A].