In this exercise we'll look at how we can transform within effect "families" using functions and Monocle optics.
We have seen several examples of effects which have a second type parameter in addition to the payload:
Either[E, ?]
has the type of the errorE
Reader[A, ?]
has the type of value to be provided/injectedA
State[S, ?]
has the type of the stateS
We can think of these effects as being families of related effects, sharing the same basic mechanism but with a particular
type focus. But, eg Reader[A, ?]
and Reader[B, ?]
, are different effects in the Eff effect stack and can't be combined.
The recommended approach is where possible, to work in terms of one effect from an effect family across your program
(ie one Reader
, one State
). This tends to be the most robust and reliable solution from a type inference perspective.
However, it's sometimes necessary or convenient to be able to combine effects from the same family, but with different foci,
together. You may need to integrate modules written by different authors that both focus on types from their own modules,
for example. So we'll look at how you can do that:
-
To transform a
Either[E, ?]
intoEither[E1, ?]
, we simply need a functionE => E1
and we canleftMap
it (Either
is a covariant functor).Writer
works similarly. -
To transform a
Reader[A, ?]
intoReader[B, ?]
, we need a functionB => A
to transform the input while we read it (Reader
is a contravariant functor). -
State[S, ?]
consumes and emits itsS
value, so a single function won't do. We need a bidirectional transform betweenS
andT
to produce aState[T, ?]
effect. This is what aLens[S, T]
is.
In this exercise, we'll look at transforming two Reader
effects to a common shared type, but the approach should extend
to any effect that has a focus type.
-
You should have a conceptual idea of how Monocle lenses work to follow this exercise.
-
Recall in previous examples we had two distinct readers for two different type of configuration, the
Filesystem
and the number,topN
, of the largest files that the scanner would keep track of. Now, there is a new classAppConfig
that models the applications config, which has these two config items as fields. We want to write the overall program purely in terms ofAppConfig
and transform itsReader
effect into readers of the other two subtypes.Find
AppConfig
and its companion. Note that it has an implicitLens
declared betweenAppConfig
andFilesystem
. -
Note that
pathScan
has a single reader effect_appconfig
, but calls methods liketakeTopN
andFileSize.ofFile
that declare different reader effects_config
and_filesystem
respectively. So effect transformation is occurring here implicitly, driven by the type system. -
The mechanism used to transform effects is
EffOptics.readerLens
. We wants totransform
the membership typeclasses, yielding a typeclass that certifies membership for the transformed focus. The~>
denotes a natural transformation, which is a "higher kinded function" from anA[_]
to aB[_]
.Note that
EffOptics.readerLens
is marked implicit, so it is wired in by the compiler whenever a method is searching for an effect member typeclass. However,EffOptics.readerLens
itself trails an implicit dependency upon aLens[S, T]
to be in implicit scope.
-
Complete the implementation of the transform
apply
method inEffOptics.readerLens
; ie how can you use aLens[S, T]
to build aReader[T]
from aReader[S, ?]
? -
Add a second lens in
AppConfig
toScanConfig
, using Monocle'sGenLens
macro. Ensure it's markedimplicit
so it can passed by the compiler when effects need to be transformed.
Run the unit tests to check that your implementation is correct.
-
Why did we use
Lens
and not justA => B
to transform ourReader
effects? A function would have worked, as the bidrectional nature ofLens
is only required forState
effects where the change of focus is two-way.The reason we avoided function was because we didn't want to depend upon any function
A => B
implicitly. It's a very generic type and likely to lead to ambiguity. Lens is a less common type so we could be more confident that there wouldn't be random lenses already in implicit scope.