Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Addresses issues #2 and #3.
The Gesture DSL is implemented with
kategory
free monads. In a gist and without much detail this is how it works.GestureDSL
from which all of it's operations are subtypes of it per the Kotlin encoding of ADTs as sealed hierarchies.In this case the operation added is
PressMenu
. Since this operation takes no arguments we can express it as anobject
instead of adata class
.object PressMenu : GestureDSL<Boolean>()
Every time this action is performed instead of actually a button getting pressed we are instantiating or placing the action in the context of the Free monad. Free monads have two possible cases. One is
Pure
which we can use to lift any valueA
toFree[GestureDSL, A]
for exampleFree.pure[GestureDSL, Int](1)
and the other isSuspend
which represents what will happen after an action is executed with it's result whichliftF
takes care of. In essence what we are doing here is elevating the action to a datastructure in memory that has not been evaluated yet but it contains on each of it's nodes all the information we need to evaluate it later.3. Finally in the interpreter we need to decide once that is evaluated what will happen when control reaches that action:
Here we are saying to go ahead and press the menu button using the device and lifting the result to the context of our evaluation via
M.pure
.In essence Free monads boilerplate is frequently:
liftF
using smart constructorsGestureDSL
to anyM[_]
in this case theSafeInterpreter
goes to any type that can supportMonadError[M, Throwable]
andTry
is one of them. That is why in therun
methods in our DSL we are usingTry
.run
is implemented you'll see that it callsfoldMap
on the final program:Here given a UIDevice we instantiate our interpreter and feed to
foldMap
. WhatfoldMap
does is iterate over the data structure where all operations have been accumulated in order evaluating one operation at a time delegating to the SafeInterpreter for the actual semantics of what evaluating means.This completely decouples program definition from program interpretation and allows you to create programs that have different interpreters.
To understand
Free
internals and how it makes this possible https://github.com/kategory/kategory/blob/master/kategory/src/main/kotlin/kategory/free/Free.kt as this is probably more advanced and outside of the scope of this PR.