I was trying to do something like this: ``` va...
# arrow
x
I was trying to do something like this:
Copy code
val foo = myEither.getOrElse { return false }
but Kotlin won't allow this because the lambda to
getOrElse
is
crossinline
. Why is it declared
crossinline
?
a
Are you trying to return early from the enclosing function, when the value doesn't exist?
x
Pretty much, yes.
Say I have a function that is supposed to attempt something, return true on success, false if any step fails. It calls helpers that give it back Either/Try/Validated values. I'd like to be able to do something like:
Copy code
val foo = myEither.getOrElse { return false }
val bar = someValidated.getOrElse { return false }
val baz = Baz(name = foo.name, size = bar.count, color = myTry.getOrElse { return false })

// computations that use foo, bar and/or baz go here

return true
@pakoito is there some better/cleaner/more-supported way to do things like this than early returns?
p
Convert those three to a single one, probably Either, and use Either.applicative()#map to fuse them together. Lemme pseudocode it
Copy code
Either.applicative().map(myEither, someValidated.toEither(), myTry.toEither()) 
{ foo, bar, baz -> 
 // computations and sheeeet
}.fold({ false }, { true })
If any of the three fails it shortcircuits and goes straight to false.
and between map and fold you can have many other operations that can fail by adding flatMap
Copy code
Either.applicative().map(myEither, someValidated.toEither(), myTry.toEither()) 
{ foo, bar, baz -> Buzz(foo, bar, baz) }
.flatMap { buzz ->
  somethingThatMayFail(buzz).toEither()
}
.fold({ false }, { true })
x
Thanks. I'll try that.
@pakoito sorry if this is a dumb question, but where is
Either.applicative()
? Kotlin's just telling me "unresolved reference", and IntelliJ can't figure out where to import it from.
p
arrow-core-instances
@xenomachina did it work?
x
trying now 🙂
One complication is that I actually have something more like:
Copy code
val foo = myEither.getOrElse { return false }
val bar = someThingThatReturnsAValidated(foo).getOrElse { return false }
val baz = Baz(name = foo.name, size = bar.count, color = myTry.getOrElse { return false })

// computations that use foo, bar and/or baz go here

return true
That is, one of them depends on an an earlier one.
I can do it by nesting maps, but it's another layer of nesting for each variable.
p
flatMap is your friend
Copy code
myEither.flatMap { foo ->
    Either.applicative()
      .map(someThingThatReturnsAValidated(foo).toEither(),
               myTry.toEither()) {
        Buzz(foo, bar, baz)
      }
}.flatMap { buzz ->
  somethingThatMayFail(buzz).toEither()
}
.fold({ false }, { true })
and rewriting it with comprehensions
Copy code
Either.monadError().bindingCatch {
  val foo = myEither.bind()
  val bar = someThingThatReturnsAValidated(foo).toEither().bind()
  val baz = myTry.toEither().bind()
  val buzz = Buzz(foo, bar, baz)
  somethingThatMayFail(buzz).toEither()
    .fold({ false }, { true })
}
x
is bind in a different module?
p
should be on arrow-core-instances
it’s an extfun only available inside binding and bindingCatch blocks
it’s an alias for `await`ing a coroutine
x
I keep getting "Error:(104, 16) Kotlin: Type inference failed: Not enough information to infer parameter L in fun <L> Either.Companion.monadError(): EitherMonadErrorInstance<L> Please specify it explicitly."
p
riiight
Either.monadError<Throwable>()
but that means that all your eithers and validated have to have something inheriting from Throwable on the left side
because that’s what
Try
expects so it’s the most general inference
x
Hmmm... my Eithers and Validated's don't have Throwables on the left, unfortunately.
p
I’d suggest you map the left for the three of them, although I’m not sure if it’s worth your while
Copy code
sealed class Wrong { 
  data class FromEither(???): Wrong()
  data class FromEither(???): Wrong()
  data class FromTry(t: Throwable): Wrong() 
}
x
Is there something like bindingCatch that works without throwables?
p
binding
in
.monad()
, but you have to do something about that
Try
. Maybe
mapLeft
after converting to
Either
the point is that you need the left side of Either, Validated and Try to be of the same type, which is the one you pass to monad
x
Perhaps I should just
mapLeft { false }
on each of them, since I'm using false for error in any case.
A downside to this is that with the non-local return I understood what my code was doing, and it was pretty straightforward. With monad()+binding+bind()+fix()+fold() I feel like I only understand about 20% of what's going on.
p
yeah, your code wasn’t wrong
monad + binding is for error propagation
instead, you’re immediately unwrapping the results and doing that
it works for blocking types like Either, or Try, but it won’t work for Rx or coroutines
because it’ll require for you to block the threads
and if you want to use the coroutines with suspend and await…that’s the same as monad + binding + bind, literally
x
Part of the problem may be that I'm migrating from Java-style of throwing exceptions to the more functional style, and this code is right at the boundary between code I've migrated and code I haven't.
That said, should I lean towards using Try or Either for errors? It seems like using both leads to complications.
p
You can trivially convert between them
Try<A> <--> Either<Throwable, A> are technically the same, but Try has a nicer API for try/catching exceptions
the point of Try is the same as checked exception: forcing the callsite to deal with them. But! and a very big BUT!
Try
is a value and is composable, which means it’s possible to store and modify it
x
The fact that it's Throwable and not Exception makes me uneasy. Catching an Error in production code seems highly unusual.
p
For that I don’t have a reply tbh. We decided early to get the whole hierarchy and for the moment we’ve stuck with the choice
x
In one of the places I'm already using Either, I'm using if for user-supplied data where right is the parsed data, and left is for when I couldn't parse their input (and is their original input). I'm not sure if that's an abuse of the way Either is meant to be used.
p
That seems like a perfectly reasonable use case. What about that validated?
x
I think that needs to be an Either. I started using Validated before I realized that it was for cases where you want to collect multiple errors.
p
okay, two down 😄
and how about the try? try is all-powerful and it catches all exceptions
can you recover from those exceptions into something reasonable?
x
Currently I'm using Try when I'm calling Java APIs that throw exceptions. This is one of the two Kotlin/Java interop pains I have. Java code throws exceptions for errors all the time, and often the only "docs" are the throws decl on the method. Since Kotlin doesn't have checked exceptions, it makes it easy to accidentally forget to check certain error conditions.
p
yeah, that’s okay
can you recover from those general catch-all exceptions into something more reasonable
for example
Copy code
sealed class CatchAll<T> { 
  data class Original<T>(val value: T): CatchAll<T>()
  object UnknownError: CatchAll<Nothing>()
}
x
The Try in the code I'm currently working on is meant to catch only one kind of exception, so I'm tempted to just use an Either<ThatException, T>
p
you still have to decide what to do with the other exceptions, maybe rethrow them and crash the program
in mapLet { }
but that has to be a conscious decision by you
although it makes much easier to find all failure points lol
x
I'm kind of thinking to just make my own helper to do the catch and wrap in an Either from the start, rather than using Try, something like:
Copy code
inline fun <reified X : Throwable, R> catchEither(block: () -> R) : Either<X, R> =
    try {
        Either.Right(block())
    } catch(exc: X) {
        Either.Left(exc)
    }
}
Wouldn't work if there's more than one kind of exception, though.
p
hear me out
Copy code
Try { /* javacode() */ }
  .recover { if (it is X) it else throw it }.toEither()
🙈 1
does the same, uses existing operators. recover === mapLeft
x
except the type of left is just
Throwable
, and not the specific exception type
p
when you do recover, because of the if check it automatically casts it to the correct type, so you get an Either with X on the left. Kotlin magic.
worst case you just have to hint it in the Either, but I don’t believe you’ll need it
x
I don't understand how it could have the type, since Try doesn't have a type param for the failure...?
p
and you just goooot me there
okay, convert to Either first, then use recover/mapLeft second
same result
Copy code
Try { /* javacode() */ }
  .toEither().mapLeft { if (it is X) it else throw it }
x
phew... I thought I was losing my mind for a sec there
😅 1
catchEither<X> { /* javacode() */ }
is still a lot shorter...
p
Copy code
inline fun <reified X : Throwable, R> catchEither(block: () -> R) : Either<X, R> =
  Try { block() }
    .toEither().mapLeft { if (it is X) it else throw it }
🧌 1
x
Well, sure, but that's an implementation detail.
p
Exactly 😄 Arrow gives you the building blocks, you have to bring your own API
We cannot ship
catchEither
with the library because it’s specific to your use case (and it’s unsafe), but
catchEither
definitely looks like something I’d have in my app, or tool, or backend
x
There is definitely something to be said for the fact that I never have to guess which parts of arrow will throw exceptions.
p
The answer should be none for anything that’s not on arrow-effects or its extension libraries. Save for unintended bugs that get immediately squashed all the exceptions are in client code.
x
I have to head out now, but thanks so much for your help!
👍 1