I've started playing with Monad transformers with ...
# arrow
a
I've started playing with Monad transformers with Arrow recently. This is a function I came up with: https://github.com/adomokos/explorer-kt/blob/master/src/main/kotlin/explorer/CsvUserImporter.kt#L36-L51 The line
EitherT.monad<ForIO, AppError>(IO.monad()).fx.monad {
looks a bit scary. Is there a simpler way to express this?
j
Currently transformers are annoying for two reasons: You basically need to write ext funcs which partially apply stuff to your monad stack otherwise you end up with what you have above. And second even in fx blocks type inference is bad, StateT.get for example almost always requires fully specified types. This can be tackled in two ways, one is that some mtl types could use variance to aid the compiler when types like nothing are involved, the other is adding a custom fx block which includes the capabilities of the stack (MonadState for example) this would already partially apply the instances and provide nice inference. So in short: no atm mtl comes with lots of boilerplate, but this can and should be changed from within arrow. I also use lots of mtl so I'd like to see that too :)
a
Thanks for the detailed response. I struggled finding good examples even. Is there a good blog post you'd recommend for something like this: https://www.fpcomplete.com/blog/2017/06/readert-design-pattern
j
I've read too few posts on mtl, just kind of pieced it together from the concrete rep a monadstack has...
EitherT<F, E, A> == Kind<F, Either<E, A>>
and so on for others. Skimmed the post: Avoiding State and Writer for the reasons listed is really good (although if you know about those caveats its manageable) Avoiding ExceptT is bs imo because the concurrent instance for EitherT (arrows ExceptT) is pretty clear about concurrent semantics + runtime errors are always a problem with or without it. The ReaderT pattern is nice, but heavily relies on typeclasses, also note that ReaderT does not compose very well. Btw just like IO is going to get an inbuilt EitherT with IO<E, A>, we are also getting an inbuilt ReaderT (Kleisli in arrow) in the form of IO<R, E, A>. On the topic of mtl: To do mtl effectively you have to be abstract in your monad stack, so you have to use constraints in the form of arguments which are typeclass instances. If you don't you loose the benefits in terms of testability and freedom to choose instances for different scenarios, which imo is the main point of mtl. Code looks like this then:
fun doSomething(MR: MonadReader<F, R>): Kind<F, A>
This will be a lot nicer in the future when instances are resolved by a compiler plugin instead of manually passing. There is however one downside:
IO
. There is no
MonadIO<F>
typeclass (not sure why) so there is no way to introduce
IO
without fixing
F
atm. (You can mark the function as suspend to, but thats not the whole package^^). I'll open an issue to track all problems I have with mtl and propose a few solutions, some require more work and I am busy for a while, so I'll likely not get to it before february 😕
The case about
MonadIO
missing is bs, we have
MonadDefer
and higher
Concurrent
etc, those model almost all
IO
behaviour so we can probably skip it in favor of those
It would still be nice for interop with
IO
code