kyleg
01/17/2020, 3:46 AMclass UseCase(private val repository: MyDatabase) {
operator fun invoke(file: File): Long { repository.getSomeLong() }
}
fun test() {
val repo: MyDatabase = TODO()
val useCase = UseCase(repo)
// some hundreds of lines later
useCase(File("/path/to/foo"))
}
I haven’t used State
yet, but the descriptions I see largely seem like it’s somewhat similar in concept. You create an object with parameters representing things you’ll read/write to (like a repository) and later do something with it like DB insertions, retrievals, all to manage app state or something?
Is my understanding correct?Christian Maus
01/17/2020, 10:38 AMJannis
01/17/2020, 10:54 AMReaderT
pattern, ZIO
(from scala) has a version of this built in in their IO
monad.
First of: a reader is a wrapper around a partially applied function: Reader<R, A> = (R) -> A
and the transformer version ReaderT<F, R, A> = (R) -> Kind<F, A>
. This means this pattern is no different from the code example @kyleg posted. The benefits are: typeclass instances (it is a monad (and a few other things), which means you can also do fx
comprehensions over it)
Second: Using a reader in arrow depends on how you use arrow:
If you use arrow fx, you should use ReaderT<ForIO, R, A>
to make this pattern work well with IO
. There is also plans to make this pattern in-built into IO
because it is so common, so using this for now and refractoring to the inbuilt version later should be easy as they are isomorphic.
If you don't use arrow fx using the normal Reader
should work fine ^^
The UseCase
class is technically no different from Reader<MyDatabase, (File) -> Long>
. This is better modeled as (File) -> Reader<MyDatabase, Long>
but those two work basically the same anyway. The benefit is, that it can be composed easilykyleg
01/18/2020, 1:15 AMJannis
01/18/2020, 2:12 AMReaderT = (R) -> Kind<F, A>
, WriterT = Kind<F, Tuple2<W, A>>
now lets compose them with `R == W`: ReaderT<WriterTPartial<F, R>, R, A> == (R) -> WriterT<F, R, A> == (R) -> Kind<F, Tuple2<R, A>> == StateT<F, R, A>
. So in theory they are the same, but the semantics of Writer
and Reader
are different which means the composition of the two does not work in practice (mainly because the output from the writer is never fed into the reader, so we actually have no changeable state). They are only equal in representation 😅
They also all evolved from similar problems and they are the most fundamental monad-transformers out there (Their Id
variants ReaderT<ForId, *, *> == Reader<*, *>
are also really easy to explain). So anyway here are the problems which led to ppl using those types:
• Constantly passing around parameters or partially applied functions for DI is solved by Reader
turning (A) -> B
into Reader<A, B>
which allows the function to use the context, but doesn't force callers to provide it (they just need to return a Reader<A, B>
like as well.
• Constantly passing around accumulated logs, values etc is solved by Writer
turning a function returning (W, B)
into a function returning Writer<W, B>
, this has the same benefits, but abstracts away the log.
• Constantly passing around and feeding changed state is solved by State
turning functions (S) -> (S, A)
into State<S, A>
again providing a nice abstraction.kyleg
01/18/2020, 3:55 AMaballano
01/18/2020, 11:55 PMJannis
01/19/2020, 12:14 AMReader
in IO
context? In that case Kleisli<ForIO, R, A>
will do just fine. ReaderT
is also just a typealias for Kleisli
anyway.kyleg
01/19/2020, 12:25 AMReader<A, B>
created with Reader { context -> returnsB() }
I have to add on .run { Id(this) }
at the end. Same goes for ReaderApi.ask<A>().map context -> returnsB() }
Did Arrow change the API for this? Because looking at this sample project using Arrow Reader, they don’t appear to have had to do this.
He actually imports Reader from arrow.data
but I have to import it from arrow.mtl
as it does not appear to exist in arrow-data
module anymore.kyleg
01/19/2020, 12:26 AMtypealias AppReader<T> = Reader<AppContext, T>
fun scanVideo(root: String): AppReader<IO<Sequence<File>>> = Reader { ctx ->
Id(IO.effect { /*snip*/ })
}
kyleg
01/19/2020, 12:28 AMJannis
01/19/2020, 12:42 AMReader
is an alias for ReaderT<ForId, R, A>
which is an alias for Kleisli<ForId, R, A>
(for category theory reasons I think). So using the constructor invokes the Kleisli
constructor which is f: (R) -> Kind<F, A>
. The easiest way to get into reader context is to use ReaderApi
as in the linked repo. Then you can also skip the Id(...)
IfReaderApi.ask<A>().map { context -> returnsB() }
returnsB
returns Reader<A, *>
(given that context
has type A
here) what you want would be ReaderApi.ask<A>().flatMap { ctx -> returnsB() }
. You could also do fx:
Reader.monad(Id.monad()).fx.monad { // Not sure if there is a shorthand somewhere, so I'll fully qualify it... You may also need more type params
val cts = ReaderApi.ask<A>().bind()
...
returnsB().bind()
}
Jannis
01/19/2020, 12:43 AMYes that is quite outdatedAlthough mtl hasn't changed too much in recent versions, so while stuff got moved, fx and some typeclasses changed, the functions from mtl are still similar, if not the same
kyleg
01/19/2020, 1:03 AM.andThen { Id(it) }
on the end of my Reader { … }
constructors. It doesn’t appear to be making use of the Reader(run: …) = ReaderT(run.andThen(Id(it)})
code that Arrow has.
I need to figure out why that is.Jannis
01/19/2020, 1:13 AM