Erik Dreyer
09/27/2022, 2:29 PMEffectScope 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.Wilerson Oliveira
09/27/2022, 2:31 PMval bar = it.invoke(command) as E
I would guess this casting is the culprit. Wouldn’t val bar = it.invoke(command).bind() work?simon.vergauwen
09/27/2022, 2:32 PMas E passes through the Success(E) because the Success case is completely inlined.
Can you debug what it.invoke(command) returns?Erik Dreyer
09/27/2022, 2:32 PMErik Dreyer
09/27/2022, 2:33 PMinvoke method:
context(ResultEffectScope)
suspend fun invoke(request: R): Esimon.vergauwen
09/27/2022, 2:36 PMinline code.
result {
val foo = ensureNotNull(commandHandlers[command::class]) { IllegalStateException("...") }
foo.invoke(command)
}simon.vergauwen
09/27/2022, 2:36 PMErik Dreyer
09/27/2022, 2:36 PMErik Dreyer
09/27/2022, 2:37 PMsimon.vergauwen
09/27/2022, 2:59 PMErik Dreyer
10/01/2022, 10:58 PMfoo 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 screenshotErik Dreyer
10/01/2022, 10:59 PMErik Dreyer
10/01/2022, 11:00 PMErik Dreyer
10/01/2022, 11:00 PMsimon.vergauwen
10/03/2022, 7:20 AMtoString and the debugger (toString) are showing two completely different things 🤯
I am making the assumption here that IDEA also uses toString in the debugger,simon.vergauwen
10/03/2022, 7:23 AM@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.Erik Dreyer
10/03/2022, 1:27 PM@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 issimon.vergauwen
10/03/2022, 1:29 PMResult 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.simon.vergauwen
10/03/2022, 1:30 PMinterface + 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 😅Erik Dreyer
10/03/2022, 1:32 PMEitherErik Dreyer
10/03/2022, 1:33 PMsimon.vergauwen
10/03/2022, 1:34 PMErik Dreyer
10/03/2022, 3:20 PMeither, 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 abovesimon.vergauwen
10/03/2022, 3:51 PMEither<Throwable, A> -> Result<A> conversion method, nor the other way around 🤔 That would be a welcoming addition.