Contextual Abstractions Overview

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Scala 3.10 (3.10.0, 2026-10-12). Every example was run with Scala CLI on JDK 26 on 2026-10-15: the Scala 3 examples on Scala 3.10.0, the Scala 2 examples on Scala 2.13.18. The syntax described here as "the 3.6 syntax" was introduced experimentally in 3.5.0 and stabilised in 3.6 (SIP-64; 3.6.0 and 3.6.1 were withdrawn, so 3.6.2 was the first release to use). It therefore works on the 3.9 LTS but not on the 3.3 LTS line, which has the older with and (using ...) forms; this is called out wherever it matters.

Contextual abstraction is the umbrella term for everything that lets the compiler supply a value, a conversion or an extra method that the programmer did not write at the call site, because it is determined by the context: the types involved and the definitions that are in scope. In Scala 2 all of it was spelled with one modifier, implicit. In Scala 3 each use case has its own construct. This page explains why, lists the constructs, shows the mapping from Scala 2, and points to the page where each construct is covered in depth. The examples use the trailhead scenario of this section: a toolkit that plans hikes on a network of mountain trails.

Why implicits were redesigned

Scala 2’s implicits are, in the words of the Scala 3 Reference, "at the same time Scala’s most distinguished and most controversial feature". The design is powerful: an implicit parameter leads to an inferred argument term that could also be written explicitly, which covers type classes, establishing a context, dependency injection, capabilities, and computing types. The Reference lists these criticisms of how that power was packaged:

  • Over-use and mis-use, especially of implicit conversions. A conditional implicit value (implicit def i1(implicit x: T): C[T]) and an implicit conversion (implicit def i2(x: T): C[T]) differ by one parameter-list keyword. Conditional values are the cornerstone of type classes, while most uses of conversions turned out to be of dubious value, yet newcomers start with conversions because they are easy to understand.

  • Over-reliance on implicit imports. Inscrutable type errors "go away with the right import incantation", and it is hard to see which implicits a program uses because they can hide in a long list of imports.

  • The syntax conveys mechanism, not intent. The single implicit modifier is attached to val, def, object, class and parameters. A type class instance is an implicit val or object when unconditional and an implicit def with implicit parameters when conditional, and the cues for intent are indirect.

  • Implicit parameters and their arguments look alike. A call such as currentMap("a") passes "a" to the implicit parameter, so one has to write currentMap.apply("a"). Only one implicit parameter section is allowed, and it must be last. Implicit parameters must be named even when the name is never used.

  • Tooling. Which implicits are available depends on the context, so completion needs the context, and a failed search deep in a recursion gave unspecific error messages (the Scala 3 compiler has since improved this and suggests imports).

The Reference concludes that none of the shortcomings is fatal, but that together they make implicit-heavy code "a lot more cumbersome and less clear than it could be", and that a radically new design is justified for newcomers. The "New Design" has four fundamental changes: given instances replace implicit definitions, using clauses replace implicit parameter lists, given imports are a new class of import selectors, and implicit conversions become instances of the standard Conversion class. The same chapter adds extension methods, context bounds, type class derivation, multiversal equality and context function types.

The same idea in one example, first in Scala 2 and then in Scala 3. Both programs do the same thing: a Show type class with an instance for kilometres, a conditional instance for lists, an enrichment method shown, and a conversion from Double to Km.

//> using scala 2.13.18
import scala.language.implicitConversions

case class Km(value: Double)
trait Show[A] { def show(a: A): String }

object Show {
  implicit val kmShow: Show[Km] = new Show[Km] { def show(k: Km) = s"${k.value} km" }
  implicit def listShow[A](implicit s: Show[A]): Show[List[A]] =
    new Show[List[A]] { def show(xs: List[A]) = xs.map(s.show).mkString("[", ", ", "]") }
}

object Syntax {
  implicit class ShowOps[A](val a: A) extends AnyVal {
    def shown(implicit s: Show[A]): String = s.show(a)
  }
  implicit def doubleToKm(d: Double): Km = Km(d)
}

object Main extends App {
  import Syntax._
  def render[A](a: A)(implicit s: Show[A]): String = s.show(a)
  println(render(List(Km(1), Km(2.5))))
  println(List(Km(3)).shown)
  val k: Km = 4.0
  println(k)
  println(implicitly[Show[Km]].show(k))
}
[1.0 km, 2.5 km]
[3.0 km]
Km(4.0)
4.0 km
//> using scala 3.10.0
import scala.language.implicitConversions

case class Km(value: Double)
trait Show[A]:
  def show(a: A): String

object Show:
  given Show[Km] = k => s"${k.value} km"                       // was: implicit val
  given [A] => (s: Show[A]) => Show[List[A]] =                 // was: implicit def with implicit parameter
    xs => xs.map(s.show).mkString("[", ", ", "]")

extension [A](a: A)                                            // was: implicit class
  def shown(using s: Show[A]): String = s.show(a)

given Conversion[Double, Km] = Km(_)                           // was: implicit def doubleToKm

def render[A](a: A)(using s: Show[A]): String = s.show(a)      // was: (implicit s: Show[A])

@main def s3(): Unit =
  println(render(List(Km(1), Km(2.5))))
  println(List(Km(3)).shown)
  val k: Km = 4.0
  println(k)
  println(summon[Show[Km]].show(k))                            // was: implicitly
[1.0 km, 2.5 km]
[3.0 km]
Km(4.0)
4.0 km

The behaviour is identical; what changed is that every definition now says what it is. The given [A] => (s: Show[A]) => line reads as "given, for every A for which a Show[A] exists, a `Show[List[A]]`".

The constructs and where they are covered

Construct What it expresses Page

given instance

A term the compiler may synthesize for a type: a type class instance, a context value, a capability

Givens & Using Clauses

using clause

A parameter list whose arguments are inferred, and the syntax to pass them explicitly with (using x)

Givens & Using Clauses

import A.given

An import selector that imports givens and nothing else

Givens & Using Clauses

Context bound T: Ord

Shorthand for a using parameter of type Ord[T]; named and aggregate bounds since 3.6

Context Bounds & Deferred Givens

extension method

Methods added to an existing type, replacing the implicit class idiom

Extension Methods

Conversion[A, B]

An implicit conversion, now an explicit and flag-controlled given instance

Conversions, into & Implicit Resolution

Context function A ?=> B

A function type whose parameter is a using parameter

Context Functions

derives

Compiler-generated given instances of a type class for a type

Type Class Derivation

The type class pattern, which is the main application of all of these, is the subject of Type Classes.

Mapping Scala 2 to Scala 3

flowchart LR IV["implicit val / object"] --> G1["given instance (alias or with a body)"] ID["implicit def with implicit parameters"] --> G2["conditional given"] IP["implicit parameter list"] --> U["using clause"] IM["implicitly"] --> S["summon"] IC["implicit class"] --> E["extension methods"] CV["implicit def A => B"] --> CO["given Conversion of A to B"] BD["context bound T : Ord"] --> BD2["context bound (unchanged, plus named and aggregate bounds)"] IMP["import A._ brings implicits"] --> GI["import A.given brings givens"] IF["implicit function types"] --> CF["context functions A ?=> B"]

The diagram shows the replacement for each Scala 2 form. The table adds the details that decide how to migrate.

Scala 2 Scala 3 Notes

implicit val x: T = ..., implicit object

given x: T = ... or given T = ..., or given T: followed by an indented body

Alias givens are evaluated lazily; the Reference suggests a regular val plus given T = x when eager evaluation matters.

implicit def f[A](implicit a: Tc[A]): Tc[F[A]]

given [A] => (a: Tc[A]) => Tc[F[A]] (3.6 syntax); given [A](using a: Tc[A]): Tc[F[A]] before 3.6

Conditional given. The conditions are context parameters, not ordinary parameters.

def f(implicit x: T)

def f(using x: T) or def f(using T)

The parameter name is optional; a method can have several using clauses, in any position.

f(arg) passed to an implicit list

f(using arg)

The explicit argument is marked, so currentMap("a") is an ordinary argument again.

implicitly[T]

summon[T]

summon returns the given with its precise type; implicitly still exists.

implicit class Ops[A](a: A)

extension [A](a: A)

An implicit class used only to add methods becomes extensions; other uses become a class plus a Conversion.

implicit def conv(a: A): B

given Conversion[A, B] = ...

Applying a conversion needs import scala.language.implicitConversions or a warning is reported.

import A._ (also imports implicits)

import A.* imports everything except givens; import A.given imports the givens

Scala 2 implicit definitions are still imported by * (see below).

Implicit function type

A ?=> B

See Context Functions.

Parameters and arguments no longer look alike

This is the second criticism in the list above, shown with real output. In Scala 2 the argument list after an implicit parameter is always the implicit list, so passing a regular argument is a type error:

//> using scala 2.13.18
object M1 extends App {
  class Context { def lookup: Map[String, Int] = Map("a" -> 1) }
  def currentMap(implicit ctx: Context): Map[String, Int] = ctx.lookup
  implicit val ctx: Context = new Context
  println(currentMap("a"))
}
[error] ./m1b.scala:6:22
[error] type mismatch;
[error]  found   : String("a")
[error]  required: M1.Context
[error]   println(currentMap("a"))
[error]                      ^^^

In Scala 3 currentMap("a") applies the result of currentMap, because arguments to a using clause must be marked with using. A method can also have a regular parameter list between two using clauses, which implicit did not allow:

//> using scala 3.10.0
class Context:
  def lookup: Map[String, Int] = Map("a" -> 1)
def currentMap(using ctx: Context): Map[String, Int] = ctx.lookup
given Context = Context()
def scaled(using ctx: Context)(key: String)(using factor: Int): Int = ctx.lookup(key) * factor
given Int = 10
@main def m2(): Unit =
  println(currentMap("a"))
  println(scaled("a"))
  println(scaled("a")(using 3))
1
10
3

How the 3.6 syntax changed the picture

The first Scala 3 releases (3.0 to 3.5) introduced given and using, but spelled conditional and structured givens with with and an extra (using ...) clause. SIP-64, experimental in 3.5.0 and stabilised in 3.6, replaced that spelling. The goal, in the words of the 3.6.2 announcement, was "to simplify and narrow the syntax rules required to create a given instance". The visible changes:

Form Before 3.6 (works on 3.3 LTS) Since 3.6 (works on 3.9 LTS and 3.10)

Given with a body

given Ord[Int] with followed by an indented body

given Ord[Int]: followed by an indented body

Conditional given

given [T](using Ord[T]): Ord[List[T]] with

given [T: Ord] => Ord[List[T]]: or given (o: Ord[Int]) => Ord[Pair]

Context bounds

T: Ord

T: Ord, T: Ord as ord (named), T: {Ord, Show} (aggregate)

By-name given

not expressible as a given

given c: () => Clock = ...

Abstract given in a trait

given ord: Ord[Element] with no right-hand side (still accepted, rejected only under -source:future)

given Ord[Element] = compiletime.deferred

The two spellings denote the same thing. On 3.10.0 the old form still compiles by default. This is the same conditional given in the 3.3 spelling, compiled on 3.10.0 without flags:

//> using scala 3.10.0
trait Ord[T]:
  def compare(a: T, b: T): Int
given Ord[Int] with
  def compare(a: Int, b: Int) = a.compare(b)
given [T](using Ord[T]): Ord[List[T]] with
  def compare(a: List[T], b: List[T]) = a.size.compare(b.size)
@main def g7(): Unit =
  println(summon[Ord[List[Int]]].compare(List(1), List(1, 2)))
-1

With -source:future the same file is rejected, which shows the direction of travel:

[error] ./h1.scala:5:16
[error] Given member definitions starting with `with` are no longer supported; use `{...}` or `:` followed by newline instead
[error] given Ord[Int] with
[error]                ^
[error] ./h1.scala:7:7
[error] This old given syntax is no longer supported; use `=>` instead of `:`
[error] given [T](using Ord[T]): Ord[List[T]] with
[error]       ^

Libraries that cross-build with the 3.3 LTS therefore keep the with spelling for now. The 3.6-style spelling is rejected by 3.3.8; for example given [T: Ord] => Ord[List[T]]: fails there with "':' expected, but '⇒' found".

Using Scala 2 code from Scala 3

Scala 3 compiles implicit definitions: "support for Scala 2’s implicits is an essential part of the common language subset between 2.13 and Scala 3", in the Reference’s words. On 3.10.0 this compiles and runs without warnings:

//> using scala 3.10.0
trait Show[A] { def show(a: A): String }
object Show {
  implicit val intShow: Show[Int] = new Show[Int] { def show(i: Int) = s"#$i" }
}
object Main {
  def render[A](a: A)(implicit s: Show[A]): String = s.show(a)
  def main(args: Array[String]): Unit = println(render(5))
}
#5

Three practical consequences:

  • Mixed code works in both directions. A using parameter is satisfied by an implicit val, and an implicit parameter is satisfied by a given. This was run on 3.10.0: with import Lib.* (an implicit val) and import New.given (a given) in scope, both a using method and an implicit method found both instances and printed #1, 'x' and #2. A Scala 3 library compiled with using clauses can also be used from Scala 2.13 code, which sees them as implicit parameter lists (this is the Reference’s "common language subset"; it was not run here).

  • import Instances.* imports implicit definitions but not givens. An implicit val imported with * satisfies a using parameter; a given does not (see Givens & Using Clauses). Migrating an object of instances from implicit to given therefore forces its importers to write import Instances.given.

  • -source:future rejects the old forms. Under that flag, implicit parameters and implicit val definitions are errors with a message that shows the replacement; the compiler can rewrite parameters with -source:future-migration -rewrite:

[error] ./s3c.scala:8:23
[error] `implicit` parameters are no longer supported, use a `using` clause instead
[error] This construct can be rewritten automatically under -rewrite -source future-migration.
[error]   def render[A](a: A)(implicit s: Show[A]): String = s.show(a)
[error]                       ^
[error] ./s3c.scala:5:16
[error] `implicit` vals are no longer supported, use a `given` clause instead. Example:
[error]
[error]    given intShow: Show[Int] = ...

Running the rewrite on that file with //> using option -source:future-migration -rewrite changed only the parameter list (def render[A](a: A)(implicit s: Show[A]) became def render[A](a: A)(using s: Show[A])) and left the implicit val untouched. The rewrite is a first step, not a complete conversion: implicit val, implicit def and implicit class definitions still have to be converted by hand.

Conversions need a flag

Of all the constructs, conversions are the one the redesign deliberately made noisy. A Conversion given is allowed, but using it implicitly is a feature that must be enabled:

//> using scala 3.10.0
//> using option -feature
given Conversion[Int, String] = _.toString
@main def s3f(): Unit =
  val s: String = 42
  println(s.length)
[warn] ./s3f.scala:6:19
[warn] Use of implicit conversion given instance given_Conversion_Int_String should be enabled
[warn] by adding the import clause 'import scala.language.implicitConversions'
[warn] or by setting the compiler option -language:implicitConversions.
[warn] See the Scala docs for value scala.language.implicitConversions for a discussion
[warn] why the feature should be explicitly enabled.
[warn]   val s: String = 42
[warn]                   ^^

The program then runs and prints 2. Without -feature the compiler prints only a one-line "feature warning" summary. Conversions get their own page: Conversions, into & Implicit Resolution.

Comparison with other languages

  • Java has no equivalent. Values that Scala passes implicitly are passed explicitly, or injected by a framework at run time; the compiler never synthesizes an argument from a type. Extension of an existing class is not possible either.

  • Kotlin has extension functions (see Extension Functions & Scope Functions) and a feature called context parameters, but no counterpart of the global, type-directed search that finds a type class instance from the companion of the type: instances in Kotlin are passed or scoped by hand.

  • Haskell type classes and Rust traits are the closest relatives of given plus a type class trait. In both, there is one global instance per type (coherence), whereas Scala resolves instances from the lexical scope and the implicit scope, so several can exist and the one in the nearest scope wins; see the resolution rules on the next page.

  • Python and JavaScript have no such feature; functools.singledispatch or duck typing play part of the role.

Where the books are outdated

Books written for Scala 2, and the first generation of Scala 3 books, differ from current Scala 3 in these points:

  • Scala 2 books show only implicit. Everything in the first table of this page is the Scala 3 replacement; implicit still compiles but is rewritten by migration tooling and rejected under -source:future.

  • Early Scala 3 material uses with for given bodies and (using ...) for conditional givens. Since 3.6 the recommended form is : and =>. Code and blog posts from 3.0 to 3.5 are still correct on the 3.3 LTS line.

  • The Scala 3 Book’s introduction still describes the redesign accurately, but it predates SIP-64, so its examples use the older with spelling in places. The semantics are unchanged.

  • Implicit classes are described as the way to add methods; in Scala 3 that is extension.

  • implicitly is shown as the way to retrieve an instance; in Scala 3 prefer summon.

The run results above are from 2026-10-15 on Scala 3.10.0 and Scala 2.13.18. The with-form example was also run on the 3.3.8 LTS (it prints -1); the 3.6-style forms were run on 3.9.0 and 3.10.0.