I’m playing around with kotlin context receivers a...
# arrow
e
I’m playing around with kotlin context receivers and Arrow’s
EffectScope
by migrating some existing code. I’m having a bit of trouble getting something to work. See the screenshot below. I think I have an instance of
Result<E>
, and the compiler agrees, but at runtime, I’m seeing some odd and conflicting information. It seems in reality, I have a
Result<Result<E>>
I’ve tried to figure out how we ended up with nested
Result
, but haven’t been able to see where this is happening.
w
val bar = it.invoke(command) as E
I would guess this casting is the culprit. Wouldn’t
val bar = it.invoke(command).bind()
work?
s
I was going to say the same, there is a good chance that
as E
passes through the
Success(E)
because the
Success
case is completely inlined. Can you debug what
it.invoke(command)
returns?
e
Ahh, I’ll try. I get a compile error without the casting, but not sure why.
The signature of that
invoke
method:
Copy code
context(ResultEffectScope)
  suspend fun invoke(request: R): E
s
Maybe a rewrite involving less
inline
code.
Copy code
result {
  val foo = ensureNotNull(commandHandlers[command::class]) { IllegalStateException("...") }
  foo.invoke(command)
}
That's also a much shorter, an clearer encoding IMO.
e
oh, nice. Thx!
@simon.vergauwen, btw, just found your FP course for Kotlin. I’m looking forward to taking that
s
This one? https://academy.47deg.com/courses/functional-programming-in-kotlin-and-arrow I'm not the trainer in the videos, but collaborated on the content. We'll also be working on new courses to add over time. If you have any questions about the curriculum I'd be happy to clarify ☺️
e
OK, I finally got back to this, and have been playing with the debugger. I’m pretty good with debugging, but I can’t make sense of this. In the screenshot below I have a variable
foo
of type
Result<UserRegisteredEvent>
The debugger shows me the variable is this same type. But
foo.toString()
shows:
Result<Result<UserRegisteredEvent>>
ok, so what does
foo.getOrNull()
give me?
java.lang.ClassCastException : kotlin.Result cannot be cast to io.liquidsoftware.base.user.application.port.in.UserRegisteredEvent
Which would indicate it is indeed a double-nested
Result
BUT, and here’s the kicker. If I force cast it to the type I think it should be, it works
(foo as Result<UserRegisteredEvent>).getOrNull()
works like I expect it to? So my question is, WTF is going on here? Please check out this screenshot
I removed the context receivers for a bit to make this more explicit for now
@simon.vergauwen Any chance you could spare a minute to look at this?
I feel like I’m taking crazy pills 🙂
s
Wow, so
toString
and the debugger (
toString
) are showing two completely different things 🤯 I am making the assumption here that IDEA also uses
toString
in the debugger,
What version of Kotlin were you using? Potentially conflicting Kotlin version on the classpath through Spring? I've had some odd things when working with
@JvmInline
and
value
class in the past year, and in some production projects we switched to using
data class
rather than
value class
due to this reason. To avoid the value class you could perhaps try using
Either.catch
instead of
runCatching
and see if
Either
doesn't have this issue. It's hard to say much beyond that without actually being able to look around in all the code to see what's going on. I've also had a bunch of issues with
inline fun
and
value class
, so you might also want to get rid of that in some places surrounding this code.
e
yeah, right? Mind blown. latest and greatest IntelliJ, with Kotlin 1.7.20 I’m not using
@JvmInline
or value classes. I may switch to Either just to see if I get different behavior. I’m close to stumped. I’ll keep poking at it to see if I can figure out where the root cause is
s
Result
is
value + @JvmInline
and I've seen this fail as well in combination with
suspend
and
inline fun
. There are a couple edge-cases, and not sure if all of them are fixed yet.
Also
interface
+
suspend
+
inline fun
+
Result / value + @JvmInline
can cause some issues through inlining and dynamic dispatching etc, but I don't know all the compiler/low-level details. I've only got some experience debugging, and spotting problematic code 😅
e
oh, interesting. OK, I’ll definitely try
Either
Thanks for the insights. I’ll post back here what happens
s
My pleasure ☺️
e
To isolate this change, and reduce the amount of updates required to test
either
, I did the following. 1. replaced a
result { … }
block with
either { … }
2. added an
Either.toResult()
extension method, so I didn’t have to change the return type of the method. (does one already exist in the library? I didn’t see it) This fixed the issue I was seeing, which probably verifies @simon.vergauwen’s suspicions above
s
Thanks for the update! There is no
Either<Throwable, A>
->
Result<A>
conversion method, nor the other way around 🤔 That would be a welcoming addition.