Edit this page on GitHub

Other Forms Of Givens

The concept of given instances is quite general. This page covers forms of givens that were not treated before.

Simple Structural Givens

Some givens simply instantiate a class without needing an alias or additional member declarations. Example:

class IntOrd extends Ord[Int]:
  def compare(x: Int, y: Int) =
    if x < y then -1 else if x > y then +1 else 0

given IntOrd()

In this case, the given clause consists of just a class creation expression, such as IntOrd() above.

Conditional Givens with Parameters

Conditional givens can also be defined with parameters. Example:

given (config: Config) => Factory = MemoizingFactory(config)

Here, (config: Config) describes a context parameter expressing a condition: We can synthesize a given Factory provided we can synthesize a given config of type Config.

Type parameters and context parameters can be combined. For instance the listOrd instance above could alternatively be expressed like this:

given listOrd: [T] => Ord[T] => Ord[List[T]]:
  ...
  def compare(x: List[T], y: List[T]) = ...

As the example shows, each parameter section is followed by an =>.

It is also possible to name context parameters:

given listOrd: [T] => (ord: Ord[T]) => Ord[List[T]]:
  ...

By Name Givens

Though in general we want to avoid re-evaluating a given, there are situations where such a re-evaluation may be necessary. For instance, say we have a mutable variable curCtx and we want to define a given that returns the current value of that variable. A normal given alias will not do since by default given aliases are mapped to lazy vals. In this case, we can specify a by-name evaluation insteadby writing a conditional given with an empty parameter list:

  val curCtx: Context
  given context: () => Context = curCtx

With this definition, each time a Context is summoned we evaluate the context function, which produces the current value of curCtx.

Given Macros

Given aliases can have the inline and transparent modifiers. Example:

transparent inline given mkAnnotations: [A, T] => Annotations[A, T] = ${
  // code producing a value of a subtype of Annotations
}

Since mkAnnotations is transparent, the type of an application is the type of its right-hand side, which can be a proper subtype of the declared result type Annotations[A, T].

Structural givens can also have the inline modifier. But the transparent modifier is not allowed for them as their type is already known from the signature.

Example:

trait Show[T]:
  inline def show(x: T): String

inline given Show[Foo]:
  inline def show(x: Foo): String = ${ ... }

def app =
  // inlines `show` method call and removes the call to `given Show[Foo]`
  summon[Show[Foo]].show(foo)

Note that inline methods within given instances may be transparent.

Pattern-Bound Given Instances

Given instances can also appear in patterns. Example:

for given Context <- applicationContexts do

pair match
  case (ctx @ given Context, y) => ...

In the first fragment above, anonymous given instances for class Context are established by enumerating over applicationContexts. In the second fragment, a given Context instance named ctx is established by matching against the first half of the pair selector.

In each case, a pattern-bound given instance consists of given and a type T. The pattern matches exactly the same selectors as the type ascription pattern _: T.

Negated Givens

We sometimes want to have an implicit search succeed if a given instance for some other type is not available. There is a special class scala.util.NotGiven that implements this kind of negation.

For any query type Q, NotGiven[Q] succeeds if and only if the implicit search for Q fails, for example:

import scala.util.NotGiven

trait Tagged[A]

case class Foo[A](value: Boolean)
object Foo:
  given fooTagged: [A] => Tagged[A] => Foo[A] = Foo(true)
  given fooNotTagged: [A] => NotGiven[Tagged[A]] => Foo[A] = Foo(false)

@main def test(): Unit =
  given Tagged[Int]()
  assert(summon[Foo[Int]].value) // fooTagged is found
  assert(!summon[Foo[String]].value) // fooNotTagged is found

Summary

Here is a summary of common forms of given clauses:

  // Simple typeclass
  given Ord[Int]:
    def compare(x: Int, y: Int) = ...

  // Parameterized typeclass with context bound
  given [A: Ord] => Ord[List[A]]:
    def compare(x: List[A], y: List[A]) = ...

  // Parameterized typeclass with context parameter
  given [A] => Ord[A] => Ord[List[A]]:
    def compare(x: List[A], y: List[A]) = ...

  // Parameterized typeclass with named context parameter
  given [A] => (ord: Ord[A]) => Ord[List[A]]:
    def compare(x: List[A], y: List[A]) = ...

  // Simple alias
  given Ord[Int] = IntOrd()

  // Parameterized alias with context bound
  given [A: Ord] => Ord[List[A]] =
    ListOrd[A]

  // Parameterized alias with context parameter
  given [A] => Ord[A] => Ord[List[A]] =
    ListOrd[A]

  // Deferred given
  given Context = deferred

  // By-name given
  given () => Context = curCtx

All of these clauses also exist in named form:

  // Simple typeclass
  given intOrd: Ord[Int]:
    def compare(x: Int, y: Int) = ...

  // Parameterized typeclass with context bound
  given listOrd: [A: Ord] => Ord[List[A]]:
    def compare(x: List[A], y: List[A]) = ...

  // Parameterized typeclass with context parameter
  given listOrd: [A] => Ord[A] => Ord[List[A]]:
    def compare(x: List[A], y: List[A]) = ...

  // Parameterized typeclass with named context parameter
  given listOrd: [A] => (ord: Ord[A]) => Ord[List[A]]:
    def compare(x: List[A], y: List[A]) = ...

  // Simple alias
  given intOrd: Ord[Int] = IntOrd()

  // Parameterized alias with context bound
  given listOrd: [A: Ord] => Ord[List[A]] =
    ListOrd[A]

  // Parameterized alias with context parameter
  given listOrd: [A] => Ord[A] => Ord[List[A]] =
    ListOrd[A]

  // Abstract or deferred given
  given context: Context = deferred

  // By-name given
  given context: () => Context = curCtx