Search code examples
scalaimplicitscala-3initialization-ordergiven

How to summon a `given` member?


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

Solution

  • Update December 2024, Scala 3.6.2 (Standard Solution)

    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