Dynamically access property on falsely typed object

I have an object FakeDefault which I later dishonestly cast to a lambda type to get the compiler to let me use it as a default argument for a context lambda parameter. I simply need to check if that default argument was used or not. In essence like so

object FakeDefault

def f(g: (x: Int) ?=> Int = FakeDefault.asInstanceOf[(x: Int) ?=> Int]) = {
  if g == FakeDefault then
    println("No argument provided")
  else
    println("Argument was provided")
}

f() // should see "No argument provided"
f(x + 1) // should see "Argument was provided"

Of course this code won’t work as we must cast g back to his true type of FakeDefault. I attempt this

object FakeDefault

def asFakeDefault[T](f: T) =
  f.asInstanceOf[FakeDefault.type]

def f(g: (x: Int) ?=> Int = FakeDefault.asInstanceOf[(x: Int) ?=> Int]) = {
  if asFakeDefault[(x: Int) ?=> Int](g) == FakeDefault then
    println("No argument provided")
  else
    println("Argument was provided")
}

This clears all problems on the type-system, it’s allowed to run. But we get a runtime error on invocation

f(x + 1)
Caused by: java.lang.ClassCastException: class Playground$$$Lambda$17889/0x00007fd17a6ccab8
cannot be cast to class Playground$FakeDefault$(Playground$$$Lambda$17889/0x00007fd17a6ccab8
and Playground$FakeDefault$ are in unnamed module of loader sbt.internal.BottomClassLoader @676f8d8e)

I also have ideas about putting a marker value on the original object and checking if it has that marker.

trait HasMarker:
  def marker: String

object FakeDefault extends HasMarker:
  val marker = "marker"

def asHasMarker[T](f: T) =
  f.asInstanceOf[HasMarker]

def f(g: (x: Int) ?=> Int = FakeDefault.asInstanceOf[(x: Int) ?=> Int]) = {
  if asHasMarker[(x: Int) ?=> Int](g).marker == "marker" then
    println("No argument provided")
  else
    println("Argument was provided")
}

But face similar error.

Does scala provide any dynamic typing or runtime reflection facilities, at all, to pass in FakeDefault as the default value for the lambda parameter, and then immediately check if that was in fact what was passed? I am also using Scalajs, if that makes a difference. Any tricks are acceptable.

(Please don’t comment “why are you doing this”, “you shouldn’t do this”, “this isn’t how static languages work”, “use Option”, etc. I already know. Please only comment if you have a way to make this work, or statement of confidence that this is 100% not possible by any technique. Thank you)

1 Like

Have you tried object FakeDefault extends (x: Int) ?=> Int ?

Scastie doesn’t seem to like it, but it might work

I feel like at this point you’re better off using either a macro, and/or applyDynamicNamed

I agree with @Sporarum that the above approach is likely to be a dead end.

The crux of the matter seems to be that when you pass a context function value as an argument to a function, it seems to be wrapped into a new object, as the following code illustrates:

type F = (x: Int) ?=> Int

val f0: F = x + 1

def sameObject[T <: AnyRef](x: T, y: T) =
  x.eq(y)

def g(f: F) =
  sameObject[F](f, f0)

g(f0) // false

PS: Even sameObject[F](f0, f0) evaluates to false.

The main problem here is again that we can’t talk about a ContextFunction value.

Because I can get your code working with normal functions using some simple tricks: Scastie - An interactive playground for Scala.

But, as soon as we change g from a Int => Int into a (x: Int) ?=> Int then it doesn’t work because it is trying to invoke g: Scastie - An interactive playground for Scala.

I think my example from above avoids this. (This is why the code is written in that weird way). :slight_smile:

But even so, the approach with the default parameter does not work if the exact value that is passed to the function is not available inside the function.

Uhm no, I don’t think that is the case, see this: Scastie - An interactive playground for Scala. I was puzzled at first, but then I tried this: Scastie - An interactive playground for Scala.
It made it clear what is happening, the problem is that still we can’t speak of the value g, it is impossible, rather the compiler simply creates a new context function wrapping g.

Which is why the comparison fails again.

Or I guess that is what you meant by:

But even so, the approach with the default parameter does not work if the exact value that is passed to the function is not available inside the function.

But that still reafirms my point.
The limitation to make this work is simply that it is impossible to talk about the value g, which is the same limitation we have with by-name parameters.

This is a real shame and a big inconsistency with the values of the language.

I agree. But I still don’t think I totally understand what is going on here :slight_smile:

Care to elaborate what is “here” and what you don’t understand?

I don’t fully understand this limitation on a semantic level and thus cannot really explain the behavior of my example above. I would have expected it to work, as long as we made sure that the context function is never actually invoked.

This is just out of curiosity. I use context functions quite often and never ran into this issue in practice.