Reputation: 8353
I defined an extension method on a context function in 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
}
}
However, when I try to use it, the compiler complains. The following @main
:
@main def main(): Unit = {
val i: List[String] ?=> Int = 1
import Scope.extFoo
i.extFoo
}
generates this error:
No given instance of type List[String] was found for parameter of (List[String]) ?=> Int
i.extFoo
Everything works fine if I call the extension method with the alternative syntax, extFoo(i)
.
Is it the expected behavior?
Upvotes: 1
Views: 190
Reputation: 470
While Dmytro's answer does allow to apply the extension method it does wrap our i
lambda into another lambda. Here's an example piece of code:
λ 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
when executed with scala-cli run test.scala
this yields:
λ 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
which might be a small surprise for some I guess. Now scala-cli compile --server=false -O -Xprint:genBCode test.scala
shows that we have more wrapping than we'd ideally want to:
[[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
becomes the closure of $anonfun$1
which in turn wraps a closure of $anonfun$1$$anonfun$1
in extFoo
call. If we modify the code to direct application of extension function as a function:
@main def main =
val x2 = extFoo(x) // should be: x.extFoo
given outside: String = "outside"
x2
the output from compiler looks like what we'd want:
@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
}
Now I guess the correct question should be why can't we refer to context lambdas without forcing application in any way. There's also an interesting thing happening when you actually do
given String = "outside"
x.extFoo
this compiles but now the output is:
in x: outside
in foo: outside
why? well, because:
"a String is fine too".extFoo
this works and prints:
in foo: a String is fine too
So, a String
is a valid value for a function that expects String ?=> String
. Why? Because the function doesn't have to use the context it's provided and therefore a String
instance is also a valid body of said function. This does generate a method when viewed from -Xprint:genBCode
level:
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)
}
)
Hope my answer helps a bit to explain why i.extFoo
compiles at least - it just applies the extension to the result of the context lambda. extFoo(i)
on the other hand works because it takes a Function1
and that's what i
is. We can't refer to it without wrapping it in another context lambda though :(
Upvotes: 4
Reputation: 51703
In i.extFoo
what doesn't compile is i
itself. i
looks for an implicit List[String]
in the scope and doesn't find such implicit.
This is consistent with docs
given ec: ExecutionContext = ...
def f(x: Int): ExecutionContext ?=> Int = ...
f(2)(using ec) // explicit argument
f(2) // argument is inferred
https://docs.scala-lang.org/scala3/reference/contextual/context-functions.html
.extFoo
is tried to be applied to the result of such application, not to the original implicit function.
....extFoo
can be resolved or not depending on existence of extension method in the scope but .extFoo
can't fix compilation of ...
.
https://scala-lang.org/files/archive/spec/3.4/07-implicits.html#views
If you mean that .extFoo
should be applied to the original implicit function then you can specify this with implicit lambda
val i: List[String] ?=> Int = 1
import Scope.extFoo
((_: List[String]) ?=> i).extFoo // compiles
Upvotes: 5