Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds pressMenu action to DSL #12

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open

Conversation

raulraja
Copy link
Collaborator

@raulraja raulraja commented Jul 4, 2017

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.

  1. All operations are expressed as types part of ADT hierarchy. The ADT in this case is 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 an object instead of a data class.
object PressMenu : GestureDSL<Boolean>()
  1. We use smart constructors to lift the operation to the context of a Free monad, and these are the methods we use when using our DSL:
fun pressMenu(): DSLAction<Boolean> = Free.liftF(GestureDSL.PressMenu)

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 value A to Free[GestureDSL, A] for example Free.pure[GestureDSL, Int](1) and the other is Suspend which represents what will happen after an action is executed with it's result which liftF 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:

is GestureDSL.PressMenu -> M.pure(device.pressMenu())

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:

  1. Define an ADT representing your free operations with no semantics as to how they are implemented but capturing all args that you need in order to evaluate them
  2. Lift those operations to the context of Free via liftF using smart constructors
  3. Write an interpreter that can prove you are covering all cases to go from your GestureDSL to any M[_] in this case the SafeInterpreter goes to any type that can support MonadError[M, Throwable] and Try is one of them. That is why in the run methods in our DSL we are using Try.
  4. If you observe how run is implemented you'll see that it calls foldMap on the final program:
fun <A> Free<GestureDSL.F, A>.run(device: UiDevice): Try<A> =
        this.foldMap(SafeInterpreter(Try, device), Try).ev()

Here given a UIDevice we instantiate our interpreter and feed to foldMap. What foldMap 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.

@Guardiola31337
Copy link
Owner

@raulraja

Free monads have two possible cases. One is Pure which we can use to lift any value A to Free[GestureDSL, A] for example Free.pure[GestureDSL, Int](1) and the other is Suspend which represents what will happen after an action is executed with it's result which liftF takes care of.

Could you go deeper on the Suspend case? When do we need it? What are some common uses? An example using Suspend would be really helpful.

@raulraja
Copy link
Collaborator Author

That was actually some gore details on how the implementation works which can be observed here:
There is really also a FlatMapped step:
https://github.com/kategory/kategory/blob/master/kategory/src/main/kotlin/kategory/free/Free.kt

You never interact with those directly and liftF is the only API you use to lift values that are suspended.
You should not have to ever deal with those unless you are inspecting the machinery behind Free.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants