Skip to content

Context Lambdas change identity on each reference; no persistent identity #22767

Open
@zylo94

Description

@zylo94

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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions