Suppose that I have some typeclass
trait FooBar[X]
and an instance of FooBar[Int]
:
given intIsFooBar: FooBar[Int] = new FooBar {}
Now, suppose that I have an interface Intf
that has some member type A
and also guarantees that there is a given FooBar[A]
:
trait Intf:
type A
given aIsFoobar: FooBar[A]
Now, I have the type Int
, and I have a FooBar[Int]
, but how do I actually implement this interface for Int
?
If I try
class IntImpl() extends Intf:
type A = Int
given aIsFoobar: FooBar[A] = summon
then I get "Infinite loop in function body IntImpl.aIsFoobar" errors, because the summon
seems to see the aIsFoobar
instead of intIsFooBar
.
If I try to summon
the instance in some auxiliary helper variable, like so:
class IntImpl() extends Intf:
type A = Int
private final val _aIsFoobar: FooBar[A] = summon
given aIsFoobar: FooBar[A] = _aIsFoobar
then I run into initialization order issues: aIsFoobar
turns out to be null
, and my application crashes with NullPointerExceptions
, which is kinda ridiculous.
I've also tried export
, but none of this works:
export FooBar[Int] as aIsFoobar // doesn't compile, invalid syntax
How do I make "the canonical" FooBar[Int]
available as the aIsFoobar
given member?
Full code:
trait FooBar[X]
given intIsFooBar: FooBar[Int] = new FooBar {}
trait Intf:
type A
given aIsFoobar: FooBar[A]
object IntImpl extends Intf:
type A = Int
given aIsFoobar: FooBar[A] = summon
Since Scala 3.6, there are now Deferred Givens, which solve exactly the problem described above. Here is what the code looks like with compiletime.deferred
:
trait FooBar[X] {
def canonical: X
}
given intIsFooBar: FooBar[Int] with {
def canonical: Int = 42
}
trait Intf:
type A
given aIsFoobar: FooBar[A] = compiletime.deferred
object IntImpl extends Intf:
type A = Int
// Nothing has to be done here, compiler will generate aIsFoobar for Int
@main def example(): Unit =
println(IntImpl.aIsFoobar.canonical) // prints 42