scalaimplicitscala-3

Can't create an anonymous implementation of ContextFunction1 in Scala 3


I'm trying to create an anonymous implementation of a context function in Scala 3. In detail, the code is the following:

def mem[E, A](block: Raise[E] ?=> A): Raise[E] ?=> A = new ContextFunction1[Raise[E], A] {
  override def apply(using r: Raise[E]): A = block(using r)
}

However, if I try to compile it, the compiler gives me the following error:

[error] 213 |}
[error]     | ^
[error]     | Found:    Object with ((in.rcard.raise4s.Raise[E]) ?=> A) {...}
[error]     | Required: A

There is no error if I switch to a classic Function1.


Solution

  • ContextFunction1 is not a part of Scala API https://docs.scala-lang.org/api/all.html, so shouldn't be used in source code manually, although it's documented at https://docs.scala-lang.org/scala3/reference/contextual/context-functions-spec.html

    Even ordinary functions are normally instantiated via lambda syntax rather than new keyword. new Function1 is compiled to invokespecial while ... => ... is compiled to invokedynamic bytecode.

    Define implicit functions via ?=> (both on type and value level):

    def mem[E, A](block: Raise[E] ?=> A): Raise[E] ?=> A = (r: Raise[E]) ?=> block(using r)
    
    def mem[E, A](block: Raise[E] ?=> A): Raise[E] ?=> A = r ?=> block(using r)
    
    def mem[E, A](block: Raise[E] ?=> A): Raise[E] ?=> A = _ ?=> block
    
    def mem[E, A](block: Raise[E] ?=> A): Raise[E] ?=> A = block
    

    In the compiler, symbols for ContextFunctionN are created here:

    https://github.com/scala/scala3/blob/main/compiler/src/dotty/tools/dotc/core/Definitions.scala#L90-L148

      /** The trait FunctionN and ContextFunctionN for some N
       *  @param  name   The name of the trait to be created
       *
       *  FunctionN traits follow this template:
       *
       *      trait FunctionN[-T0,...-T{N-1}, +R] extends Object {
       *        def apply($x0: T0, ..., $x{N_1}: T{N-1}): R
       *      }
       *
       *  That is, they follow the template given for Function2..Function22 in the
       *  standard library, but without `tupled` and `curried` methods and without
       *  a `toString`.
       *
       *  ContextFunctionN traits follow this template:
       *
       *      trait ContextFunctionN[-T0,...,-T{N-1}, +R] extends Object {
       *        def apply(using $x0: T0, ..., $x{N_1}: T{N-1}): R
       *      }
       *  ImpureXYZFunctionN follow this template:
       *
       *      type ImpureXYZFunctionN[-T0,...,-T{N-1}, +R] = {cap} XYZFunctionN[T0,...,T{N-1}, R]
       */
      private def newFunctionNType(name: TypeName): Symbol = {
        ...
    

    Being not able to instantiate ContextFunction1 seems to be expected behavior

    // `ContextFunctionN` does not have constructors
              !ctor.exists || zeroParams(ctor.info)
    

    https://github.com/scala/scala3/blob/main/compiler/src/dotty/tools/dotc/core/Types.scala#L6011

    /** ... check that ... no user-written class extends ContextFunctionN.
       */
      def checkParents(cls: Symbol, parentTrees: List[Tree])(using Context): Unit = cls.info match {
        ...
    

    https://github.com/scala/scala3/blob/main/compiler/src/dotty/tools/dotc/typer/RefChecks.scala#L120

    For FunctionN sources are generated for Scala 2 standard library (used in Scala 3 too). For ContextFunctionN sources are not even generated.

    At runtime ContextFunctionN do not exist (Class.forName("scala.ContextFunction1") throws ClassNotFoundException). ContextFunctionN are erased to FunctionN.

    The error message could be better. Such messages were shown often in early days of Dotty e.g. for polymorphic functions: Type lambda with higher kind