stojan
03/28/2020, 4:32 PMIO<E, A>
API with the Functional and Reactive Domain Modeling sample project.... it looks great, way better than Monad transformers IMO https://github.com/arrow-kt/frdomain.kt/pull/11raulraja
03/28/2020, 4:46 PMfun Account.calculateInterest(): IO<AccountServiceException, Amount>
raulraja
03/28/2020, 4:46 PMJannis
03/28/2020, 4:46 PMIO<E, A>
> EitherT<ForIO, E, A>
using concrete types in mtl (apart from at the edge) is an anti-pattern imo. Mtl code should look like this fun m(ME: MonadError<F, E>, MIO: MonadIO<F>): Kind<F, A>
(or similar) and then at the edge of your application (similar to running IO) you run the monad stack by filling in the instances. If m
would be at the edge you'd do m(EitherT.monadError(), EitherT.monadIO(IO.monadIO()))
. This is still ugly, but just picture this with arrow-meta and automatic typeclass resolution, you would not to need manually fill this in. This is still more involved than IO<E, A>
if you just need IO
+ MonadError<E>
, but as soon as you layer more effects (if you ever need them) it becomes much more powerfulraulraja
03/28/2020, 4:48 PMsuspend fun Account.calculateInterest(): Either<AccountServiceException, Amount>
raulraja
03/28/2020, 4:48 PMsuspend () -> Either<E, A>
<-> IO<E, A>
stojan
03/28/2020, 5:26 PMIO<A>
(haven't converted it, yet)stojan
03/28/2020, 5:28 PMraulraja
03/28/2020, 5:32 PMraulraja
03/28/2020, 5:35 PMKleisli<IOPartialOf<E>, D, A>
is the same as D.() -> IO<E, A>
or D.() -> suspend () -> Either<E, A>
raulraja
03/28/2020, 5:37 PMstojan
03/28/2020, 5:38 PMraulraja
03/28/2020, 5:40 PMR
be the environment and adhere to any capabilities you want. The have an edge of the world Module that proofs that R
can be those capabilities by delegation https://gist.github.com/raulraja/97e2d5bf60e9d96680cf1fddcc90ee67#file-direceivers-kt-L51raulraja
03/28/2020, 5:41 PMstojan
03/28/2020, 5:42 PMraulraja
03/28/2020, 5:42 PMraulraja
03/29/2020, 10:13 AMraulraja
03/29/2020, 10:13 AM@extension
interface IOMonadError<E> : MonadError<IOPartialOf<E>, Throwable>, IOApplicativeError<E>, IOMonad<E>
raulraja
03/29/2020, 10:13 AMThrowable
should be E
raulraja
03/29/2020, 10:34 AMraulraja
03/29/2020, 10:34 AMraulraja
03/29/2020, 10:36 AMraulraja
03/29/2020, 10:36 AMJannis
03/29/2020, 12:15 PMstojan
03/29/2020, 12:25 PMsimon.vergauwen
03/29/2020, 12:26 PMsimon.vergauwen
03/29/2020, 12:27 PMEither
OR IO
not bothsimon.vergauwen
03/29/2020, 12:27 PM() -> Either<E, A>
or suspend () -> A
but never suspend () -> Either<E, A>
simon.vergauwen
03/29/2020, 12:27 PMstojan
03/29/2020, 12:35 PME
error channel (that way you can handle and eliminate it later) or crashJannis
03/29/2020, 12:35 PMJannis
03/29/2020, 12:37 PMstojan
03/29/2020, 12:39 PME
channel maybe?
otherwise you have 2 possible type signature for a function that reads a string form a file: IO<Nothing, String>
and IO<IOException, String>
and the later is clearly superior since it can eliminate the error using handleErrorWith
Jannis
03/29/2020, 12:39 PMstojan
03/29/2020, 12:39 PMJannis
03/29/2020, 12:41 PMmove them in theThat does not work, we and neither the user can predict if something can fail and with what exception so how would you move it to E? You could say that constructingchannel maybe?E
IO<Nothing, A>
means the exceptions should be fatal, but that again is an api that will end up with users doing the wrong thing unintentionally. Imo it is more important to never fail to catch exceptions because otherwise bracket becomes unpredictable and that would be awful.Jannis
03/29/2020, 12:42 PMstojan
03/29/2020, 12:46 PMThat does not work, we and neither the user can predict if something can fail and with what exception so how would you move it to E?having a different function for creating IO from third party code that creates
IO<Throwable, A>
safelystojan
03/29/2020, 12:49 PMJannis
03/29/2020, 12:50 PMJannis
03/29/2020, 12:54 PMstojan
03/29/2020, 1:10 PMraiseError
and raiseException
for creating your IO
you also have 2 sets of error handling recovery operators, 2 sets of "side effect on error" operators etc....simon.vergauwen
03/29/2020, 1:17 PMJannis
03/29/2020, 1:17 PMraise
and handleWith
everything else is derived from monad error anyway. And I don't think the Throwable
part should get any helpers beyond raising and lifting it to the typed channel (IO<Nothing, A> -> IO<Throwable, A>
). You are right, this increases api surface, but I don't think E
and Throwable
should be treated equally as we do actually want users to prefer E
. The whole thing is a balancing act, but it is a lot safer to simply catch allsimon.vergauwen
03/29/2020, 1:20 PMIO<E, A>
<~> IO<Nothing, Either<E, A>>
<~> EitherT<ForIO, E, A>
<~> IO<Either<E, A>>
<~> suspend () -> Either<E, A>
. Merging E
is Throwable
is not valid, and is only possible at the cost of totality. In other words you loose referential transparency, and thus the substitution model and all we love about IO
.stojan
03/29/2020, 1:23 PMsimon.vergauwen
03/29/2020, 1:24 PMIO<E, A>
documentation in the next couple weeks as well.simon.vergauwen
03/29/2020, 1:24 PMstojan
03/29/2020, 1:25 PMraiseException
<- you probably don't wanna use this, take a look at raiseError
instead 😄