Question about context receivers on lambdas. I ha...
# getting-started
d
Question about context receivers on lambdas. I have the following code:
Copy code
context(Int)
fun prefixIt(): (String) -> String = { prefix: String -> prefix + getIt() }

context(Int)
fun getIt() = this@Int

fun caller(prefixer: (String) -> String) = prefixer("_")

fun test() {
    with(1) {
        caller(prefixIt())
    }
}
Since
prefixIt
doesn't accept any parameters I would like to convert it to a property, e.g. like this:
Copy code
val prefixIt: Int.(String) -> String = { prefix: String -> prefix + getIt() }
Trying to do that I realized that this isn't semantically the same. Whereas currently I provide the Int context when creating the prefixer, the new approach wants the context when the prefixer gets actually called. This got me curious about how my current version even works. So I inspected the decompiled bytecode (in thread) and didn't find the answer 🙂
I specifically don't understand
prefixIt
. The
$context_receiver_0
seems unused there...
Copy code
@NotNull
   public static final Function1 prefixIt(int $context_receiver_0) {
      return TestKt::prefixIt$lambda$0;
   }

   public static final int getIt(int $context_receiver_0) {
      return $context_receiver_0;
   }

   @NotNull
   public static final String caller(@NotNull Function1 prefixer) {
      Intrinsics.checkNotNullParameter(prefixer, "prefixer");
      return (String)prefixer.invoke("_");
   }

   public static final void test() {
      byte var0 = 1;
      int $this$test_u24lambda_u241 = var0;
      int var2 = false;
      caller(prefixIt($this$test_u24lambda_u241));
   }

   private static final String prefixIt$lambda$0(int $$context_receiver_0, String prefix) {
      Intrinsics.checkNotNullParameter(prefix, "prefix");
      return prefix + getIt($$context_receiver_0);
   }
y
The difference is that your current code is "curried", and so it asks for the
context(Int)
immediately, but then it produces a lambda that only asks for a
String
parameter. Your other version tries to ask for both, and hence it doesn't work. Btw, your new version can work if you just do:
Copy code
val prefixIt: context(Int) (String) -> String = ...
context(C) operator fun <C, T, R> (context(C) (T) -> R).invoke(arg: T): R = this(this@C, arg)
// usage
caller { prefixIt(it) }
The only reason that that
invoke
function is needed here is because it hasn't been implemented yet in Kotlin, but it should be coming eventually
d
Thanks for the working version! I wouldn't have come with that myself and hopefully the UX will be improved in the new context parameters. But my original question was a little bit different. I'm aware of the semantic difference between the two versions and I wanted the "curried" one. I was just curious about how does it work internally, therefore I was looking at the bytecode. IMO it can't work as given above because the
$context_receiver_0
param of
prefixIt
is unused. WDYT?
y
Oh, I think your decompiler isn't showing the full details, that's what's happening. Let me decompile this with CFR and look at the result (specifically, inside
prefixIt
, it should be passing that value to the lambda)
👍 1
d
Anyway, it's quite clear what it actually does. It just performs a partial evaluation inside
prefixIt
.
(At least that's what I would expect.)
y
Here's it decompiled:
Copy code
/*
 * Decompiled with CFR 0.152.
 * 
 * Could not load the following classes:
 *  kotlin.Metadata
 *  kotlin.jvm.functions.Function1
 *  kotlin.jvm.internal.Intrinsics
 *  org.jetbrains.annotations.NotNull
 */
import kotlin.Metadata;
import kotlin.jvm.functions.Function1;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;

@Metadata(mv={2, 0, 0}, k=2, xi=48, d1={"\u0000\u0018\n\u0000\n\u0002\u0018\u0002\n\u0002\u0010\u000e\n\u0002\u0010\b\n\u0002\b\u0006\n\u0002\u0010\u0002\n\u0000\u001a\u001b\u0010\u0000\u001a\u000e\u0012\u0004\u0012\u00020\u0002\u0012\u0004\u0012\u00020\u00020\u0001R\u00020\u0003\u00a2\u0006\u0002\u0010\u0004\u001a\u000f\u0010\u0005\u001a\u00020\u0003R\u00020\u0003\u00a2\u0006\u0002\u0010\u0006\u001a\u001a\u0010\u0007\u001a\u00020\u00022\u0012\u0010\b\u001a\u000e\u0012\u0004\u0012\u00020\u0002\u0012\u0004\u0012\u00020\u00020\u0001\u001a\u0006\u0010\t\u001a\u00020\n\u00a8\u0006\u000b"}, d2={"prefixIt", "Lkotlin/Function1;", "", "", "(I)Lkotlin/jvm/functions/Function1;", "getIt", "(I)I", "caller", "prefixer", "test", "", "library_test"})
public final class FooKt {
    @NotNull
    public static final Function1<String, String> prefixIt(int $context_receiver_0) {
        return arg_0 -> FooKt.prefixIt$lambda$0($context_receiver_0, arg_0);
    }

    public static final int getIt(int $context_receiver_0) {
        return $context_receiver_0;
    }

    @NotNull
    public static final String caller(@NotNull Function1<? super String, String> prefixer) {
        Intrinsics.checkNotNullParameter(prefixer, (String)"prefixer");
        return (String)prefixer.invoke((Object)"_");
    }

    public static final void test() {
        int $this$test_u24lambda_u241 = 1;
        boolean bl = false;
        FooKt.caller(FooKt.prefixIt($this$test_u24lambda_u241));
    }

    private static final String prefixIt$lambda$0(int $$context_receiver_0, String prefix) {
        Intrinsics.checkNotNullParameter((Object)prefix, (String)"prefix");
        return prefix + FooKt.getIt($$context_receiver_0);
    }
}
and it does what you'd expect
d
Thanks. It's good to know that the built-in Intellij decompiler is not reliable.