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