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