330674 (2) [Avatar] Offline
#1
I'm a bit confused about the use of continuations in the Event ADTs, and was wondering if you could help deconfuse me.

Most of the Event definitions (in the listings and in the github sources) have as part of the Events a function for transforming an aggregate to the 'Next' type, like this:

trait Event[+Next]
case class Opened[Next](num: String, onOpen: Account => Next) extends Event[Next]
case class Closed[Next](num: String, onClose: Account => Next) extends Event[Next]


But in the "pure" interpreter example (see the src file here), it doesn't always include the continuation. It looks like this:

trait Event[+Next]
case class Opened[Next](no: String, onInit: String => Next) extends Event[Next]
case class Closed[Next](no: String, next: Next) extends Event[Next]


Could you elaborate on the purpose of the continuations? Is there something about the impure implementations which require them?

I'm confused due to the fact that they're defined as part of the Event ADT (the pure data). But the interpreter both supplies the continuation value and is in charge of invoking them (which it doesn't need to do). So why isn't that logic strictly in the interpreter? Why explicitly define it as part of the ADT?


Thanks in advance for any clarification and for the great book!
Debasish Ghosh (111) [Avatar] Offline
#2
I am not sure I understand the first part of your question. Let me start w/ an explanation of the continuation part.

The purpose of the continuation is to give you the ability to chain free monads. With the account example you can write composite actions like the following ..

val composite =
    for {
      n <- open("a-123", "debasish ghosh", Some(today))
      _ <- credit(n, 10000)
      _ <- credit(n, 30000)
      _ <- debit(n, 23000)
} yield (())


because of the continuation that you pass with each of the event types. The basic idea of free monads is to decouple the AST from the evaluation. The above composite action only builds the AST and the continuation helps chaining the various nodes of the AST. So intuitively the continuation is required for the monad part of the abstraction.

Let me know if u need more details on this.

Thanks.
330674 (2) [Avatar] Offline
#3
Yep, I understand that the Next type parameter is necessary for chaining Events together with Free Monads. My question specifically was why there were three different styles, as discussed similarly in this other post.

Let's boil this down as small an example as possible. Take this situation: I want to define an ADT for logging strings, and then build a Free Monad from the ADT.

Case 1: Hard-code the resulting Next types
triat LogEvent[+Next]
case class Logged(text: String) extends LogEvent[Unit]

Case 2: Next type is unrestrained and unused
triat LogEvent[+Next]
case class Logged[Next](text: String, next: Next) extends LogEvent[Next]

Case 3: Next type is unrestrained but used to define continuation function
triat LogEvent[+Next]
case class Logged[Next](text: String, onLog: Unit => Next) extends LogEvent[Next]


All three of these higher-kinded types can be lifted into a Free Monad. And all three styles are used at least once in either the text or the associated online sources.
Case 1 is the most simple, but doesn't give a way to encapsulate side effects properly. Also, defining the functor is, er, funky.
Cases 2 and 3 don't seem right, since I could use them to build the syntax tree in a way that produces a Logged[Int], when that really doesn't have any meaning in this domain.

So, my questions are:
1. Why the three different styles?
2. Can I use the Next type to keep the types constrained in a way that makes sense in the given domain?

Hope this is more clear. Thanks again!
Debasish Ghosh (111) [Avatar] Offline
#4
I think I now understand your question .. let me explain with an example from the book .. Listing 5.6

sealed trait AccountRepoF[+A]  #A
case class Query[+A](no: String, onResult: Account => A) extends AccountRepoF[A]  #B
case class Store[+A](account: Account, next: A) extends AccountRepoF[A]
case class Delete[+A](no: String, next: A) extends AccountRepoF[A]


Here for the first case Query, the continuation is of the form Account => A. This is because the Query operation (or more precisely the lifted query function in Listing 5.8 ) will give you an Account instance within the free monad, which needs to be passed along with the continuation for further processing. Hence the continuation is of the form Account => A. A typical example will be a composite operation that queries an account and then does something with it. So in the chain we need to pass the Account instance as well.

For Store and Delete, we typically model them as Unit methods - hence we don't need anything to pass along with the continuation. Just the monadic chaining will do. Hence we pass just the Next.

There's a third type where we don't need the continuation at all. That's the leaf type of the AST. The tree ends there. So we don't need the continuation.

HTH.