:hushed: what does a `context(Raise<T>) @Co...
# arrow
c
😯 what does a
context(Raise<T>) @Composable
do? Does it even work? Throwing exceptions inside of composables is not allowed (it bubbles up weirdly). so I'm not sure if this is allowed or not.
s
I'm not sure. I am not familiar with
@Composable
. It also doesn't behave normally with
CoroutineScope
, right? You need to use
LaunchedEffect
?
c
It's not that behaves weirdly with CoroutineScope, it's just that you can't have both
@Composable
and
suspend
on a single function. But since post 2.0 we won't need
suspend
on
shift
…
s
But since post 2.0 we won't need
suspend
on
shift
…
We're still doing some investigation 😅
If you find use-cases in
@Composeable
why it should remain
suspend
, or where removing
suspend
is problematic I'd love to hear it
If you find use-cases in
@Composeable
why it should remain
suspend
Or elsewhere.
c
Oh no, since composables cannot be
suspend
, we currently can't use
shift
inside of them (though I don't even know if that would make sense), so removing this requirement potentially makes it possible to have error management directly in composables (but again, composable functions are weird so I don't know if it even makes sense)
s
How would you imagine using it there? 🤔
c
My use case: Currently I have a lot of composables that look like:
Copy code
// Convenience function to use Either services with Compose
@Composable
fun <E, T> rememberEither(compute: suspend () -> Either<E, T>): Either<E, T> {
    var state by remember { mutableStateOf(/* trust me, I have an initial value here */) }

    LaunchedEffect {
        state = compute()
    }

    return state
}

@Composable
fun Foo(id: String) {
    val data = rememberEither { service.getById(id) }
        .orNull()

    if (data != null) {
        Data(…)
    } else {
        LoadingSpinner()
    }
}
The large
if
in the composable appears every time there is an API result (since they're all modeled as either-returning functions). It's a bit ugly, and feels like what the
either{}
block is here to avoid, yet composables don't seem to like it 😅
Much like
suspend
represents "a computation that takes some time",
EffectScope<T>
represents "a computation that may fail and short-circuit",
@Composable
is "a computation that may have new results over time" (if my understanding is correct, it's isomorphic to a
StateFlow
, hence Molecule), but it doesn't seem like
EffectScope
and
@Composable
compose (eh) that well together.
Just to be clear, I'm definitely not criticizing Arrow here! Compose is weird, after all, and we can't have everything fit together perfectly—but I still want to dream
Maybe the solution would be to introduce a composable fold:
Copy code
@Composable
fun <T, O> Either<T, O>.fold(
  onLeft: @Composable (T) -> Unit,
  onRight: @Composable (O) -> Unit,
)
I'll experiment with that.
Actually the Arrow
fold
is inline, right? So that may already be available. At some point I should experiment more with that and write a Arrow + Compose blog post.
That feels like the final boss of Kotlin though xD Arrow + Compose + Coroutines for nice errors with automatic reactivity
s
Yes, Arrow's fold is
inline
. This is a very interesting use-case for
Either
inside
Compose
! I would say using
Either
rather then
Raise
is safer, because then you're working with values between recomposition.
m
I think that using Arrow in Compose is bad however you look at it. small toying around is fine but anything else is bad
pears and apples
c
I guess the best upgrade possible is just to create a
Copy code
@Composable
fun LaunchedEffect(block: context(Raise<T>) suspend () -> Unit)
@Marko Novakovic why would it be bad? I need error management, and Compose makes exception unusable. Arrow Core has already replaced exceptions in the rest of the codebase, so of course I want to use it here too.
m
sure, knock yourself out
I may be wrong tho. I would like to see that blogpost you mentioned writing
it seems like you are mixing logic and Compose UI, which is always weird
c
Compose UI has to display errors at some point, so it must know about them
but yeah, my split between UI and domain is not great
m
it has to know about errors, true… 🤔
but throwing implies logic, IO etc. drawing wouldn’t throw right?
c
Oh yeah, definitely.
m
but I still want to read that blogpost
s
Is drawing not considered IO?
m
yeah… drawing is IO/side effect but good one right? also necessary one so it’s not the same as reading DB, writing to file etc. right? but still I get the point. Compose is just not meant to be used like that, I think
same way Arrow is guarding against side effects with
suspend
Compose is doing that with
@Composable
s
How is reading from DB or writing to file not necessary? 😅
c
Compose doesn't really care about purity/side-effects, it just really hates exceptions because functions don't invoke each other, so the stacktrace makes no sense
I mean side-effects are still a bad habit, but they won't break your app
m
How is reading from DB or writing to file not necessary? 😅
I fucked up there, kind of. it’s not as necessary as UI, let’s say it like that. it’s important ofc but program would be usable without DB but without UI not that much
just ignore that part, it’s not well said/thought through
what I wanted to say is that you can’t avoid all side effects, some are necessary. and Compose is already guarding agains it same as Arrow is with Kotlin’s
suspend
again, mixing logic and UI is weird and mostly wrong
c
Well to sum up this thread: what I'm searching for is a nice syntax to go from
suspend () -> Either<E, T>
to having the value in a local variable in a
@Composable
function, that doesn't require additional nesting to manage the different cases. IMO it's really the only thing needed for Arrow and Compose to fit well together. I'll continue searching, but I'm sure there's a way
m
Copy code
@Composable
fun Composable() {
    Text(text = "Hello World!")
}
would never throw, for example. only reason it would is if you are getting text from somewhere else and UI should not care about it. that’s my point
good luck
c
^ and if that example does throw, Compose will crash, it's not recoverable, so it's very important to never let exceptions bubble up to the view
on Desktop, the app just crashes, on the web it stops responding to inputs
haven't tried on Android yet (yes I'm part of the people who use Compose for everything but Android 😅) but I assume it's similar
m
valid points. I still stand by: • UI should not know about all of that and errors should be stopped outside of it. UI should just represent that error happened and shouldn’t know how it happened. pure drawing, no
raise
, no
throw
etc. • I want to read that blogpost, I may be wrong and that may be really cool approach. for sure sounds pretty cool
c
I agree with point 1, but the service/viewmodel/whatever must be able to communicate to the view "the value should be <A>, but it could also fail with <B>". In vanilla Kotlin, this would be done with a custom sealed-class for every possible kind of result, and
Either
is basically just a generic version of that with nice syntax sugar, so I don't think it's an egregious request 🙂
j
@simon.vergauwen if you want to play with this but without fighting creating any UI, maybe you can try Square/CashApp molecule library and some Flow/StateFlow with Compose + Raise. Context receivers already work on Compose too
Maybe the context receivers branch can be cloned and rewrite it with molecule 🤔