Description
Compiler version
3.6.3
Minimized code
With ordinary lambdas, they have distinct values on the runtime and you can trivially compare a lambda against itself, put it in collections and so on.
val f: (x: Int) => Int = x => 3 + x
f == f // true
f // Playground$$$Lambda$15092/0x00007f26d3fc7000@be515d5
f // Playground$$$Lambda$15092/0x00007f26d3fc7000@be515d5
var fns = Set[(x: Int) => Int]()
for i <- 1 to 10 do
fns = fns + f
fns.size // 1
The last part using a set is a good demonstration for verifying correct behavior. Sets by definition do not allow duplicates, so 1
is what we should expect to see. Anything else would be extremely counter-intuitive in a language where functions are values.
However, lambdas with context parameters behave very differently.
val f: (x: Int) ?=> Int = 3 + x
var fns = Set[(x: Int) ?=> Int]()
for i <- 1 to 10 do
fns = fns + f
fns.size // 10
Adding the same context lambda to a set 10 times results in a set of 10 elements.
And if we ask if that lambda is itself, it will evaluate to false
, and show that Scala is actually changing the value identity in the runtime with every reference of f
val f: (x: Int) ?=> Int = 3 + x
var seq = Seq[(x: Int) ?=> Int]()
def first(seq: Seq[Any]) = seq(0)
seq = f +: seq
val a = first(seq)
seq = f +: seq
val b = first(seq)
a == b // false
first(seq) == first(seq) // true
a // Playground$$$Lambda$14026/0x00007f26d3d09ab8@490ed173
b // Playground$$$Lambda$14197/0x00007f26d3d46000@15165343
Some explanation: referencing context lambdas and passing them around without invoking them is very difficult in Scala, as the moment you refer to them the Scala compiler will immediately search for the required implicits in the current scope in attempting an invocation. To work around that for this experiment, we can put the context function into a sequence of context functions, cast that sequence to
Seq[Any]
, then retrieve index 0 from it so Scala does not know it's a context function.
Another experiment someone showed
val f : Int ?=> Int = ???
def equal[T](a: T, b: T): Boolean = {
a == b
}
equal[Int?=>Int](f,f) // false
More said on a thread I made here https://users.scala-lang.org/t/preventing-invocation-of-context-functions-referenced/10625
Why does Scala change the identifier for the context lambda each time? Is it a new wrapper? Can we avoid this?
It's unintuitive behavior, and it is currently bringing my project where I ran into this bug to a complete stop with no obvious solution, as my use case requires me receiving a context lambda and checking if it is the same context lambda I had stored previously, where a simple ==
check would do the job but this looks impossible coincidentally.