Preventing invocation of context functions referenced

With normal lambdas I can talk about them in the runtime without invoking them

val f: (x: Int) => Int = x + 1

print(f == f) // true
print(f) // Playground$$$Lambda$103....

When I attempt this with a contextual function, Scala eagerly attempts to invoke f immediately when I am not wanting to do invocation yet.

val f: (x: Int) ?=> Int = x + 1

print(f == f) // No given instance of type Int

What mechanisms or tricks does Scala provide to talk about or inspect contextual lambdas in the runtime without causing the compiler to attempt to invoke them?

It’s not being invoked, this is just how given values work. The compiler will look for them and check them first. If a given instance is not there, then it will error. This is the case even if it’s not a lambda but just a normal def.

scala> def f(using n: Int) = n
def f(using n: Int): Int
                                                                                       
scala> f
-- [E172] Type Error: -----------------------------------------------------------------
1 |f
  | ^
  | No given instance of type Int was found for parameter n of method f
1 error found

You see, a context lambda is just syntax sugar for writing this kind of def.

I suspect you are trying to use context functions without knowing them too well, in an unintended way. They are really not meant to be used like this.

This is quite a curiosity, i thought I’d be able to trick the compiler, but it looks like it does some wrapping at the call site so different references are created (this feels like a place that could be optimised though).

It does feel like an odd use case but i guess this is somewhat curious behaviour.

I successfully tricked the compiler by putting the function into a Seq and then retrieving them at index 0 when the Seq is casted to Seq[Any]

val f: (x: Int) ?=> Int = 3 + x

var seq = Seq[(x: Int) ?=> Int]()

seq = f +: seq

def first(seq: Seq[Any]) = seq(0)

first(seq) == first(seq) // true

It is very strange that yours does not work…

IMHO it’s not so unreasonable to want to be able pass around a context function as a value without immediately applying the context arguments. Isn’t that even the main point of having function values in a programming language?

I bumped into this in another thread as well recently.

2 Likes

The compiler will look for them and check them first.

it is doing this for the purposes of invoking the function. If it cannot find the required givens, it doesn’t have sufficient information to invoke, analogous to calling a function without giving it the required arguments.

def f(x: Int) = 3
f() // missing argument!

With the contextual function, look how f on it’s own evaluates to the return type Int when referenced

val f: (x: Int) ?=> Int = 2 + x


given Int = 3

f // 5

Rather than f evaluating to some Playground$$$Lambda$11... value in memory (like it would if this function had explicit parameters and we had not yet invoked it), it evaluates to 2 + 3, hence invocation.

It is the same behavior of a function with no parameters at all

def f = 3

f // 3

Which might pose a derivative question; how to avoid invoking a parameterless function? For example, I don’t want this to be true

def f = 3
def g = 3

f == g // true, but I wan't false

I want to compare the functions by memory, not value. Like how this would evaluate

val f = () => 3
val g = () => 3

f == g // false
f() == g() // true

OK, this is extremely weird.

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

what???

Yeah, there is a similar problem with by-name parameters, you can’t just reference the thunk but the value produced by evaluating it.

I proposed for that to be fixed in Scala 3 but it was rejected in favour of keeping the syntax “lightweight”, which I am sure would be the same answer in this case.

1 Like

While I understand your point, note that the problem here is that these are methods not functions. And you can’t compare methods, because they aren’t values.

Also, a parameterless method is by all intents and purposes the same as a value.


Again, not possible in this context, because this is a method, not a function.

There are no parameterless functions, only functions of empty parameters; e.g. () => A

Scastie with the code: Scastie - An interactive playground for Scala.

So, I think that there is some wrapping happening here, and thus a new wrapper object is created when you re-add the function, thus the memory address you are comparing is of the wrapper, which is of course different.

BTW, if you want to compare by memory address use eq rather than == it is clearer and faster.


I think the best you can do to solve this and your other problems is create a custom type with an implicit conversion from context functions into it.
I will try to sketch it later.

1 Like

I think the best you can do to solve this and your other problems is create a custom type with an implicit conversion from context functions into it.

This sounds excellent, if it’s possible. I similarly tried to get the context functions to implicitly convert into Some(...), but that broke the implicit argument resolution. Facing similar difficulties with your idea, I’ve tried

import scala.language.implicitConversions

class F(val f: (x: Int) ?=> Int)

case class Foo(id: Int):
  var f: F = F(x + 1)

  def modify(
    f: F,
  ) =
    this.f = f

given Conversion[(x: Int) ?=> Int, F] with
  def apply(f: (x: Int) ?=> Int) = F(f)

val foo = Foo(0).modify(
  f = x + 1
  // Not found: x
)

Am I doing the conversion code correctly?

Sadly no, I wasn’t able to get it to work: Scastie - An interactive playground for Scala.

There seems to be some weird issues with named tuples.
But, the main issue is now that the implicit conversion won’t kick it if the types are not known, but we actually wanted the implicit conversion to infer the types.

So this feels like a dead end.

I made an issue on the github Context Lambdas change identity on each reference; no persistent identity · Issue #22767 · scala/scala3 · GitHub