我在 Scala 3 中的上下文函数上定义了一个扩展方法:

object Scope {
  extension [E, A](a: List[E] ?=> A) def extFoo: A = foo(a)
  
  private def foo[E, A](a: List[E] ?=> A) = {
    given s: List[E] = List.empty
    println(a)
    a
  }
}

但是,当我尝试使用它时,编译器会抱怨。下列@main

@main def main(): Unit = {
  val i: List[String] ?=> Int = 1
  import Scope.extFoo
  i.extFoo
}

生成此错误:

No given instance of type List[String] was found for parameter of (List[String]) ?=> Int
  i.extFoo

如果我使用替代语法 调用扩展方法,一切都会正常工作extFoo(i)

这是预期的行为吗?

1

  • 3
    我认为这与编译器如何处理对上下文函数的引用有关。您可以自由地将这样的上下文 lambda 分配给一个值scala scala> val x: List[String] ?=> Int = 1 val x: (List[String]) ?=> Int = Lambda$1355/0x00000070014b8410@c4cceb ,但任何尝试访问x没有所需给定值的范围的操作都会失败并出现此错误。最能做的就是将其分配给另一个具有匹配上下文函数声明的 def 或 val。其他一切都应用上下文(如果不存在则失败)。这很有趣,甚至:type i在repl中都不起作用!


    – 


2 个回答
2

i.extFoo不能编译的就是i本身。在范围内i寻找隐式,但没有找到这样的隐式。List[String]

这与文档一致

given ec: ExecutionContext = ...

def f(x: Int): ExecutionContext ?=> Int = ...

f(2)(using ec)   // explicit argument
f(2)             // argument is inferred

.extFoo尝试应用于此类应用的结果,而不是原始隐式函数。

....extFoo是否可以解析取决于范围内扩展方法的存在,但.extFoo无法修复....

如果您的意思是.extFoo应该应用于原始隐式函数,那么您可以使用隐式 lambda 指定它

val i: List[String] ?=> Int = 1
import Scope.extFoo
((_: List[String]) ?=> i).extFoo // compiles

2

  • 感谢您的回复。我仍然不明白为什么extFoo(i)会起作用。


    – 

  • 1
    这很有趣,repl 已经将其视为i对象Lambda。将其包装到另一个 lambda 中应该会创建另一个 lambda 对象,我对所显示的内容进行了简短的了解-Xprint:genBCode。还有一个额外的 lambda。


    – 

虽然 Dmytro 的答案确实允许应用扩展方法,但它确实将我们的ilambda 包装到另一个 lambda 中。这是一段示例代码:

λ cat test.scala
extension (x: String ?=> String) def extFoo: String = foo(x)

def foo(f: String ?=> String): String =
  given inside: String = "inside"
  val r = f
  println(s"in foo: ${r}")
  r

val x: String ?=> String =
  println(s"in x: ${summon[String]}")
  summon[String]

@main def main =
  val x2: String ?=> String = ((_: String) ?=> x).extFoo

  given outside: String = "outside"
  x2

当执行scala-cli run test.scala此产量时:

λ scala-cli run .
Compiling project (Scala 3.4.1, JVM (17))
Compiled project (Scala 3.4.1, JVM (17))
in x: inside
in foo: inside

我想这对某些人来说可能是一个小惊喜。现在scala-cli compile --server=false -O -Xprint:genBCode test.scala表明我们的包装比我们理想的要多:

[[syntax trees at end of                  genBCode]] // /Users/lbialy/Projects/foss/tmp/extensions-on-ctx-funs/test.scala
package <empty> {
  @SourceFile("test.scala") final module class test$package extends Object {
    def <init>(): Unit =
      {
        super()
        x =
          {
            closure(this.$init$$$anonfun$1)
          }
        ()
      }
    private def writeReplace(): Object =
      new scala.runtime.ModuleSerializationProxy(classOf[test$package])
    extension (x: Function1) def extFoo: String = foo(x)
    def foo(f: Function1): String =
      {
        lazy var inside$lzy1: scala.runtime.LazyRef =
          new scala.runtime.LazyRef()
        val r: String = f.apply(this.inside$1(inside$lzy1)).asInstanceOf[String]
        println("in foo: ".+(r))
        r:String
      }
    private <static> val x: Function1
    def x(): Function1 = x
    @main def main(): String =
      {
        lazy var outside$lzy1: scala.runtime.LazyRef =
          new scala.runtime.LazyRef()
        val x2: Function1 =
          {
            closure(<empty>.this.$anonfun$1)
          }
        x2.apply(this.outside$1(outside$lzy1)).asInstanceOf[String]
      }
    private final def $init$$$anonfun$1(using contextual$2: String): String =
      {
        println("in x: ".+(contextual$2))
        contextual$2
      }
    private final def inside$lzyINIT1$1(inside$lzy1$1: scala.runtime.LazyRef):
      String =
      inside$lzy1$1.synchronized[String](
        (if inside$lzy1$1.initialized() then inside$lzy1$1.value() else
          inside$lzy1$1.initialize("inside")).asInstanceOf[String]
      )
    private final lazy given def inside$1(inside$lzy1$2: scala.runtime.LazyRef)
      : String =
      (if inside$lzy1$2.initialized() then inside$lzy1$2.value() else
        this.inside$lzyINIT1$1(inside$lzy1$2)).asInstanceOf[String]
    private final <static> def $anonfun$1$$anonfun$1(using _$1: String): String
       = x().apply(_$1).asInstanceOf[String]
    private final <static> def $anonfun$1(using contextual$3: String): String =
      extFoo(
        {
          closure(this.$anonfun$1$$anonfun$1)
        }
      )
    private final def outside$lzyINIT1$1(outside$lzy1$1: scala.runtime.LazyRef)
      : String =
      outside$lzy1$1.synchronized[String](
        (if outside$lzy1$1.initialized() then outside$lzy1$1.value() else
          outside$lzy1$1.initialize("outside")).asInstanceOf[String]
      )
    private final lazy given def outside$1(outside$lzy1$2: scala.runtime.LazyRef
      ): String =
      (if outside$lzy1$2.initialized() then outside$lzy1$2.value() else
        this.outside$lzyINIT1$1(outside$lzy1$2)).asInstanceOf[String]
  }
  @SourceFile("test.scala") final class main extends Object {
    def <init>(): Unit =
      {
        super()
        ()
      }
    <static> def main(args: String[]): Unit =
      try
        {
          test$package.main()
          ()
        }
       catch
        {
          case
            error @ _:scala.util.CommandLineParser.CommandLineParser$ParseError
             => scala.util.CommandLineParser.showError(error)
        }
  }
  final lazy module val test$package: test$package = new test$package()
}

x2成为其闭包,而闭包又包装了调用$anonfun$1的闭包。如果我们修改代码以直接将扩展函数作为函数应用:$anonfun$1$$anonfun$1extFoo

@main def main =
  val x2 = extFoo(x) // should be: x.extFoo
  given outside: String = "outside"
  x2

编译器的输出看起来像我们想要的:

    @main def main(): String =
      {
        lazy var outside$lzy1: scala.runtime.LazyRef =
          new scala.runtime.LazyRef()
        val x2: String =
          extFoo(
            {
              closure(<empty>.this.$anonfun$1)
            }
          )
        x2:String
      }

现在我想正确的问题应该是为什么我们不能在不以任何方式强制应用程序的情况下引用上下文 lambda。当你真正这样做时,还会发生一件有趣的事情

given String = "outside"
x.extFoo

这可以编译,但现在的输出是:

in x: outside
in foo: outside

为什么?好吧,因为:

"a String is fine too".extFoo

这有效并打印:

in foo: a String is fine too

因此,aString是期望 的函数的有效值String ?=> String。为什么?因为该函数不必使用它提供的上下文,因此String实例也是该函数的有效主体。从层次上看,确实-Xprint:genBCode生成了一个方法:

    private final <static> def main$$anonfun$2(using contextual$5: String):
      String = "a String is fine too"

    // in main()
        extFoo(
          {
            closure(<empty>.this.main$$anonfun$2)
          }
        )

希望我的回答至少有助于解释为什么i.extFoo编译 – 它只是将扩展应用于上下文 lambda 的结果。extFoo(i)另一方面是有效的,因为它需要 aFunction1而这就是事实i。不过,如果不将其包装在另一个上下文 lambda 中,我们就无法引用它:(