我在 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
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 的答案确实允许应用扩展方法,但它确实将我们的i
lambda 包装到另一个 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$1
extFoo
@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 中,我们就无法引用它:(
|
scala scala> val x: List[String] ?=> Int = 1 val x: (List[String]) ?=> Int = Lambda$1355/0x00000070014b8410@c4cceb
,但任何尝试访问x
没有所需给定值的范围的操作都会失败并出现此错误。最能做的就是将其分配给另一个具有匹配上下文函数声明的 def 或 val。其他一切都应用上下文(如果不存在则失败)。这很有趣,甚至:type i
在repl中都不起作用!–
|