Attila Domokos
01/13/2020, 7:05 PMJannis
01/13/2020, 7:42 PMRIOApi.monadError().fx.monad {...}
(not sure what the exact code is). That should enable the bind syntax for Kind<EitherTPartialOf<ForIO, AppError>, *>
.
After that you have EitherT<ForIO, AppError, Int>
(assuming you added the two in the fx block), so you need to lift that into ReaderT<EitherTPartialOf<ForIO, AppError>, GetAppContext>
, that should be as easy as doing ReaderT.lift(RIOApi.monadError(), res)
(I am not 100% sure ReaderT
has lift, but it should have something similar, if it does not work just use ReaderT { res }
)
This will be made easier when arrow has a MonadTrans
typeclass which is made to deal with those lifting situations: https://github.com/arrow-kt/arrow/issues/1913 is the ticket tracking it with some discussion ^^
This is not how I'd do mtl tho, because the types are too concrete and it's a hassle to use. Imo a better way is do define things over polymorphic F
and only fix the type at the very end. The functions then look like fun <F> oneT(ME: MonadError<F, AppError>): Kind<F, Int>
and if you need context you do fun <F> doSomething(MR: MonadReader<F, GetAppContext>): Kind<F, ??>
and so on. If you need more than one capability just add more parameters. With arrow-meta you will get those resolved implicitly soon anyway, so the cost of having to pass parameters will eventually go away. (Btw if you need IO
you can use MonadDefer
and above (Async
, Concurrent
etc) to do almost anything you need, but if you need to interface with actual hardcoded IO
methods you need to wait for me to finish my MonadIO
pr or use unsafe methods :/) Then top-level you can fill in your actual monad-stack. This is also great for testing because you can use a different monad-stack for tests and because of the laws it should work exactly the same ^^Attila Domokos
01/13/2020, 7:53 PMJannis
01/13/2020, 8:07 PMI am already turned off by the complexity of the calls and lack of examplesThat I can totally understand 🙂 mtl in arrow needs a lot of work and the docs even more. If you want to make the stacking part manageable consider defining a new concrete datatype
class App<A>(val unApp: ReaderT<EitherTPartialOf<ForIO, AppError>, GetAppContext, A>)
and quickly define the typeclasses you need for it (by just delegating to the underlying value). Then just return App
everywhere you need to be in the stack. Here is an example on how that would look for WriterT
+ EitherT
+ polymorphic M
. It is a bunch of boilerplate (even in haskell) But once you have set it up it's usable (just some upfront work). But here again I have abstracted the behaviour of the concrete type into the MonadTest
typeclass to keep users from having to use a concrete stack.
For resources and blogs, I wish I had good mtl resources, I'll look around if I find any, skim them and link it if they are any good. mtl is hard, and with all the boilerplate around higherkinds and typeclasses that kotlin imposes it's even harder to learn...Jannis
01/13/2020, 8:17 PMF
and one for F<A>
because in kotlin we can't partially apply types)Jannis
01/13/2020, 8:28 PMfun <F, R> MonadReader<F, R>.ask(): Kind<F, R>
being the only used thing in modern haskell (compared to concrete reps)
In practice only the last one is used in modern Haskell.This is a nice little example for how the typeclass approach leads to very nice testing (and it also shows that not even haskell solved the boilerplate problem, athough it's not too bad ^^) https://github.com/lexi-lambda/mtl-style-example I need to get going, so I'll top searching, as always just ask away with any question, and maybe I should at some point bookmark those threads because those questions are a good inspiration on what needs to be in the docs.