Neil
03/03/2020, 3:29 PMAttila Domokos
03/03/2020, 3:35 PMAttila Domokos
03/03/2020, 3:36 PMdeserializeData
returns Left
the call chain will return that Left
value.Neil
03/03/2020, 3:49 PM.map{}
, but that adds quite a lot of boilerplate around each function call.Attila Domokos
03/03/2020, 3:49 PMmap
, monadic functions are invoked with flatMap
.thanerian
03/03/2020, 3:50 PMthanerian
03/03/2020, 3:52 PMBob Glamm
03/03/2020, 3:52 PMBob Glamm
03/03/2020, 3:52 PMNeil
03/03/2020, 3:53 PMAttila Domokos
03/03/2020, 3:55 PMEither
values, or just operate on functions without the Either
context. I am not sure that is more boilerplate than doing try/catch
calls in functions.Bob Glamm
03/03/2020, 3:56 PM.flatMap
in the fx
block
fun f1(...): Either<Throwable, Int> = ...
fun f2(x: Int): Either<Throwable, String> = ...
fun f3(y: String): Either<Throwable, Boolean> = ...
Either.fx<Throwable, Boolean> {
val (a) = f1(...)
val (b) = f2(a)
val (c) = f3(b)
c
}
Jannis
03/03/2020, 3:57 PMf1().flatMap(f2).flatMap(f3)
isn't it? fx
doesn't always make code cleanerBob Glamm
03/03/2020, 3:58 PMNeil
03/03/2020, 3:59 PMEither
. So I need to chain them and individually wrap each one in flatMap()
or map()
as in your example to have the later ones abort if the first returns Left.
But when throwing an Exception I would just have one try/catch block wrapping the whole series.Bob Glamm
03/03/2020, 4:00 PMIO
, not Either
Bob Glamm
03/03/2020, 4:00 PMIO
the boilerplate is still overall less than a top-level `try`/`catch`Bob Glamm
03/03/2020, 4:01 PMattempt
and handleErrorWith
are available to deal with error conditionsBob Glamm
03/03/2020, 4:01 PMBracket
and Resource
Bob Glamm
03/03/2020, 4:02 PMBracket
with `try`/`catch` only is very difficult to writeNeil
03/03/2020, 4:02 PMfx{}
aborts if anything returns a Left
? That would do what I want... but it looks like I need to assign a var to the return of every function to trigger that?Jannis
03/03/2020, 4:02 PMEither
is fine. Catching them is not something Either
should be used to do. IO
is much nicer in that regard because you can define handlers at very specific levels, it automatically catches exceptions and is pure code.Bob Glamm
03/03/2020, 4:03 PMval (...) = expr
syntax de-sugars to repeated flatMap
Bob Glamm
03/03/2020, 4:04 PMf1.flatMap(f2).flatMap(f3)
is cleanerNeil
03/03/2020, 4:04 PMBob Glamm
03/03/2020, 4:04 PM!
or .bind()
Neil
03/03/2020, 4:05 PMflatmap()
lots of times is quite verbose/boilerplate-y for sync code.Bob Glamm
03/03/2020, 4:05 PMf2
above returned Unit
then !f2(a)
instead of val (b) = f2(a)
Bob Glamm
03/03/2020, 4:06 PMfx
comprehensionsBob Glamm
03/03/2020, 4:07 PMflatMap
far outweighs the (currently) somewhat unusual syntaxBob Glamm
03/03/2020, 4:07 PMIO
Attila Domokos
03/03/2020, 4:08 PMflatMap
can be noisy when your coming from Haskell, but like @Bob Glamm mentions it, there is quite a bit of functionality you can rely on.Neil
03/03/2020, 4:09 PMJannis
03/03/2020, 4:11 PMEither
you kind of loose the benefits^^ Also using Throwable
in left is kind of sub-par. A more explicit type might be betterNeil
03/03/2020, 4:12 PMEither<MyDomainError, Unit>
. Compiler likes val ignoreme = f2(a)
but not !f2(a)
Or should I look at IO
instead?Bob Glamm
03/03/2020, 4:13 PMf2(a).bind()
instead of !f2(a)
. Sorry, it's been a couple of months since I've used ArrowJannis
03/03/2020, 4:13 PMEither<L1, A>
and Either<L2>
btw, Either
only composes if the left is the same.Bob Glamm
03/03/2020, 4:14 PMIO
for operations that yield traditional side-effects: e.g. opening/reading from a file, read/write database, HTTP endpoints, etc.Bob Glamm
03/03/2020, 4:15 PMEither
in cases like signature verification or decryption - things that can fail but aren't necessarily a traditional side-effectBob Glamm
03/03/2020, 4:15 PMNeil
03/03/2020, 4:17 PMJannis
03/03/2020, 4:17 PM(although in the latter case I get lazy and just use MonadError anyway)With polymorphic
F
or of a fixed type? Because that usually leads to mtl style later? o.OBob Glamm
03/03/2020, 4:18 PMJannis
03/03/2020, 4:18 PMbind
is only available inside fx
and only for the correct `L`: Either.fx<L, R>
only provides Either<L, *>.bind()
Neil
03/03/2020, 4:19 PMIO
it does seem to be about async things and at the moment everything in our codebase is very much sync.Bob Glamm
03/03/2020, 4:19 PMBob Glamm
03/03/2020, 4:20 PMNeil
03/03/2020, 4:20 PMBob Glamm
03/03/2020, 4:21 PMBob Glamm
03/03/2020, 4:23 PMIO
has a number of typeclass instances, so it handles Async, Sync, Monad*, Applicative*, etc.Jannis
03/03/2020, 4:24 PMIO
in arrow has a few purposes:
• It defers computation (and thus makes it lazy and pure) Good for referential transparenty and thus easy refractoring
• It provides powerful methods of handling errors/exceptions with resource safety
• It provides easy methods to run code concurrently (and keeping the guarantees from errors handling over async bounds)
It is generally a good idea to use it if your program involves side-effects and you may want to go async at some point. For error handling stick to what @Bob Glamm referred to above: IO
for code you don't own and that involves side-effects (like throwing) and a different exception type for error paths in pure code (like Option
, Either
)Jannis
03/03/2020, 4:25 PMMonadDefer
for some reason^^Bob Glamm
03/03/2020, 4:25 PMdef f1(...): Kind<ForIO, Int> = ...
def f2(...): Kind<ForIO, String> = ...
IO.fx {
val (a) = f1(...)
val (b) = f2(a)
b
}
Bob Glamm
03/03/2020, 4:26 PMBob Glamm
03/03/2020, 4:26 PMNeil
03/03/2020, 4:26 PM.bind()
into f2()
so that the caller doesn't have to assign to an unused var or !
or .bind()
?Bob Glamm
03/03/2020, 4:27 PMBob Glamm
03/03/2020, 4:28 PMfx
block is constructing a program that will be executed later within the given effect (Either, IO)Bob Glamm
03/03/2020, 4:28 PM.fold
(for Either) or something like .unsafeRunSync
(for IO)Bob Glamm
03/03/2020, 4:29 PMJannis
03/03/2020, 4:30 PMfx
block. But that is hard to do in arrow atmNeil
03/03/2020, 4:30 PMNeil
03/03/2020, 4:30 PMfx
block.Neil
03/03/2020, 4:31 PM!
or .bind()
.Bob Glamm
03/03/2020, 4:32 PMJannis
03/03/2020, 4:32 PMfx {
val fa: IO<A> = ...
val x by fa // binds
fa // also binds
}
Is valid syntax. This means forgetting !
bind
is not a problem.
Right now you are correct, it is easy to forgetJannis
03/03/2020, 4:33 PMAttila Domokos
03/03/2020, 4:33 PM<-
to do it.Jannis
03/03/2020, 4:33 PMdo
let fa :: IO a = ...
x <- fa -- binds
fa -- binds
Bob Glamm
03/03/2020, 4:33 PMBob Glamm
03/03/2020, 4:33 PMBob Glamm
03/03/2020, 4:35 PMfx
and lazy evaluation results in "programs" (and by programs, I mean small sets of function call chains) that can be chained/attached together easilyBob Glamm
03/03/2020, 4:36 PMIO
will still result in the application running immediately and synchronously, but the composition of functions inside the application and their evaluation is lazy instead of eagerBob Glamm
03/03/2020, 4:37 PMIO.fx {}
example ^^ up there composes all of the functions together, and that whole block would be run synchronously and immediately upon calling .unsafeRunSync
on the whole thingBob Glamm
03/03/2020, 4:37 PM.unsafeRunSync
Neil
03/03/2020, 4:42 PMNeil
03/03/2020, 4:43 PMJannis
03/03/2020, 4:47 PMEither
will work just fine. Just remember how to compose them map
, flatMap
, traverse
, mapN
, fx
will be your most useful methods. Also use domain errors and stay away from wrapping everything regardless of need in Either
. Then it will be a good replacement. If you want to use IO
later on you will get a nice 1-1 mapping with IO<E, A>
as soon as we start releasing 0.11 snapshots, which is planned right after the next releaseNeil
03/03/2020, 4:49 PMNeil
03/03/2020, 4:51 PMNeil
03/03/2020, 4:53 PMIO
helping with exception handling but I don't want to have to convert all code to .map/.flatMap style.Bob Glamm
03/03/2020, 4:55 PMBob Glamm
03/03/2020, 4:56 PMBob Glamm
03/03/2020, 4:56 PM.fold
or .unsafeRunSync
that can be called at well-defined boundaries within try/catchNeil
03/03/2020, 4:58 PM.fold
run on IO
and cause execution like .unsafeRunSync
does?Bob Glamm
03/03/2020, 4:59 PM.fold
runs on Either
Bob Glamm
03/03/2020, 5:00 PM.unsafeRunSync
runs on IO
Bob Glamm
03/03/2020, 5:00 PMBob Glamm
03/03/2020, 5:01 PMEither
also has methods like getOrElse
and getOrHandle
that also force evaluationNeil
03/03/2020, 5:11 PMfold{}
as it gives a clear place to handle error vs success, and fx{}
as it provides a way to abort the block when anything returns a Left
.Bob Glamm
03/03/2020, 5:15 PMBob Glamm
03/03/2020, 5:16 PMNeil
03/03/2020, 5:51 PMNeil
03/03/2020, 6:09 PM.bind()
or !
or is concerning me :-(Jannis
03/03/2020, 6:13 PMNeil
03/03/2020, 6:18 PMMike
03/04/2020, 11:55 PM!
or bind
as the returned type will be an Either
if you forget it, and therefore not directly usable by the next statement.
Although it's more code, I prefer the explicitness of Either and custom error codes. The caller knows something can go wrong, and can decide how to handle it, or let it 'bubble' up. Exceptions still exist for truly exceptional, nothing the caller can do about it, situations.
I like to think of Either as 'checked exceptions implemented in a usable, harder to abuse manner'