Contextual Abstractions

Scala 3 replaces Scala 2 "implicits" with clearer, intent-specific features: given, using, extension methods, type classes, context functions, and conversions.

Scala 3 replaces Scala 2 "implicits" with clearer, intent-specific features: given, using, extension methods, type classes, context functions, and conversions.

26.1 Type classes with given/using

// 1. The type class
trait Show[A]:
  extension (a: A) def show: String

// 2. Instances
object Show:
  given Show[Int] with
    extension (a: Int) def show = s"Int($a)"

  given Show[String] with
    extension (a: String) def show = s"\"$a\""

  given [A](using sa: Show[A]): Show[List[A]] with   // derived instance
    extension (as: List[A]) def show = as.map(_.show).mkString("[", ", ", "]")

// 3. Using instances
def printAll[A: Show](xs: List[A]): Unit = xs.foreach(x => println(x.show))
def describe[A](a: A)(using s: Show[A]): String = s"Value: ${a.show}"

List(1, 2, 3).show         // [Int(1), Int(2), Int(3)]
describe("hi")
summon[Show[Int]]          // retrieve an instance explicitly (Scala 2: implicitly)

26.2 Named and anonymous givens, alias givens

given intOrdering: Ordering[Int] = Ordering.Int.reverse     // alias given
given Ordering[String] = Ordering.String                     // anonymous
given ExecutionContext = ExecutionContext.global
lazy given Config = loadConfig()                              // lazily initialized

26.3 Using clauses

case class Config(url: String, timeout: Int)

def connect(name: String)(using cfg: Config): String = s"$name -> ${cfg.url}"
def run()(using Config) = connect("db")        // anonymous using parameter, passed along

given Config = Config("jdbc:pg://localhost", 30)
run()
connect("cache")(using Config("redis://", 5))  // explicit

26.4 Given imports

object Instances:
  given Show[Boolean] with { extension (b: Boolean) def show = if b then "yes" else "no" }

import Instances.given                 // all givens
import Instances.{given Show[Boolean]} // a specific one
import Instances.*                     // does NOT import givens in Scala 3

26.5 Resolution rules

The compiler searches for givens in: (1) the lexical scope (local definitions, imports), then (2) the implicit scope of the type — companion objects of the type class and of the type arguments. Ambiguities are compile errors; more specific instances win.

26.6 Type class derivation

Libraries provide derived: derives Codec.AsObject (circe), derives ReadWriter (upickle), derives Eq, Order (kittens). See §32.4 for writing derived.

case class Person(name: String, age: Int) derives CanEqual, Show   // Show needs a `derived` method
enum Color derives CanEqual:
  case Red, Green

26.7 Context functions

Used for builders/DSLs and capability passing.

type Transactional[A] = Transaction ?=> A

def transfer(amount: Int): Transactional[Unit] =
  summon[Transaction].log(s"transfer $amount")

def inTx[A](body: Transactional[A]): A =
  given Transaction = Transaction()
  body

26.8 Implicit conversions (use sparingly)

import scala.language.implicitConversions
given Conversion[String, Int] = _.length
val n: Int = "hello"      // 5

26.9 Multiversal equality

import scala.language.strictEquality
case class Id(v: Int) derives CanEqual
Id(1) == Id(2)       // OK
// Id(1) == "x"      // compile error under strictEquality

26.10 By-name context parameters & NotGiven

import scala.util.NotGiven
def onlyIfNoOrdering[A](using NotGiven[Ordering[A]]): String = "no ordering available"