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

Update free monad docs #832

Merged
merged 1 commit into from
Feb 1, 2016
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 45 additions & 105 deletions docs/src/main/tut/freemonad.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,30 +77,21 @@ recursive values.
We need to create an ADT to represent our key-value operations:

```tut:silent
sealed trait KVStoreA[+Next]
case class Put[T, Next](key: String, value: T, next: Next) extends KVStoreA[Next]
case class Get[T, Next](key: String, onResult: T => Next) extends KVStoreA[Next]
case class Delete[Next](key: String, next: Next) extends KVStoreA[Next]
sealed trait KVStoreA[A]
case class Put[T](key: String, value: T) extends KVStoreA[Unit]
case class Get[T](key: String) extends KVStoreA[Option[T]]
case class Delete(key: String) extends KVStoreA[Unit]
```

The `next` field in each of the types provides a way to link an
operation with successive values. The `Next` type parameter can be
anything at all, including `Unit`. It can be thought of as a carrier,
a way to link a single operation with successive operations.

As we will see, the `next` field is also necessary to allow us to
provide a `Functor` instance for `KVStoreA[_]`.

### Free your ADT

There are six basic steps to "freeing" the ADT:

1. Create a type based on `Free[_]` and `KVStoreA[_]`.
2. Prove `KVStoreA[_]` has a functor.
3. Create smart constructors for `KVStore[_]` using `liftF`.
4. Build a program out of key-value DSL operations.
5. Build a compiler for programs of DSL operations.
6. Execute our compiled program.
2. Create smart constructors for `KVStore[_]` using `liftF`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An alternative to liftF is to use Free.inject when targeting cats.data.Coproduct as a composition mechanism maybe it should be mentioned here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it's mentioned below. Perhaps we should consider adding some kind of TOC at the beginning of each tutorial to show at a glance the different sections and topics being described.

3. Build a program out of key-value DSL operations.
4. Build a compiler for programs of DSL operations.
5. Execute our compiled program.

#### 1. Create a `Free` type based on your ADT

Expand All @@ -110,33 +101,7 @@ import cats.free.Free
type KVStore[A] = Free[KVStoreA, A]
```

#### 2. Prove `KVStoreA[_]` has a `Functor`.

One important thing to remark is that `Free[F[_], A]` automatically
gives us a monad for `F`, if `F` has a functor (i.e. if we can find or
construct a `Functor[F]` instance). It is described as a *free monad*
since we get this monad "for free."

Therefore, we need to prove `KVStoreA[_]` has a functor.

```tut:silent
import cats.Functor

implicit val functor: Functor[KVStoreA] =
new Functor[KVStoreA] {
def map[A, B](kvs: KVStoreA[A])(f: A => B): KVStoreA[B] =
kvs match {
case Put(key, value, next) =>
Put(key, value, f(next))
case g: Get[t, A] => // help scalac with parametric type
Get[t, B](g.key, g.onResult andThen f)
case Delete(key, next) =>
Delete(key, f(next))
}
}
```

#### 3. Create smart constructors using `liftF`
#### 2. Create smart constructors using `liftF`

These methods will make working with our DSL a lot nicer, and will
lift `KVStoreA[_]` values into our `KVStore[_]` monad (note the
Expand All @@ -147,31 +112,31 @@ import cats.free.Free.liftF

// Put returns nothing (i.e. Unit).
def put[T](key: String, value: T): KVStore[Unit] =
liftF(Put(key, value, ()))
liftF[KVStoreA, Unit](Put[T](key, value))

// Get returns a T value.
def get[T](key: String): KVStore[T] =
liftF(Get[T, T](key, identity))
def get[T](key: String): KVStore[Option[T]] =
liftF[KVStoreA, Option[T]](Get[T](key))

// Delete returns nothing (i.e. Unit).
def delete(key: String): KVStore[Unit] =
liftF(Delete(key, ()))
liftF(Delete(key))

// Update composes get and set, and returns nothing.
def update[T](key: String, f: T => T): KVStore[Unit] =
for {
v <- get[T](key)
_ <- put[T](key, f(v))
vMaybe <- get[T](key)
_ <- vMaybe.map(v => put[T](key, f(v))).getOrElse(Free.pure(()))
} yield ()
```

#### 4. Build a program
#### 3. Build a program

Now that we can construct `KVStore[_]` values we can use our DSL to
write "programs" using a *for-comprehension*:

```tut:silent
def program: KVStore[Int] =
def program: KVStore[Option[Int]] =
for {
_ <- put("wild-cats", 2)
_ <- update[Int]("wild-cats", (_ + 12))
Expand All @@ -182,30 +147,9 @@ def program: KVStore[Int] =
```

This looks like a monadic flow. However, it just builds a recursive
data structure representing the sequence of operations. Here is a
similar program represented explicitly:

```tut:silent
val programA =
Put("wild-cats", 2,
Get("wild-cats", { (n0: Int) =>
Put("wild-cats", n0 + 12,
Put("tame-cats", 5,
Get("wild-cats", { (n1: Int) =>
Delete("tame-cats", n1)
})
)
)
})
)
```

One problem with `programA` you may have noticed is that the
constructor and function calls are all nested. If we had to sequence
ten thousand operations, we might run out of stack space and trigger a
`StackOverflowException`.
data structure representing the sequence of operations.

#### 5. Write a compiler for your program
#### 4. Write a compiler for your program

As you may have understood now, `Free[_]` is used to create an embedded
DSL. By itself, this DSL only represents a sequence of operations
Expand Down Expand Up @@ -236,17 +180,17 @@ def impureCompiler =
new (KVStoreA ~> Id) {
def apply[A](fa: KVStoreA[A]): Id[A] =
fa match {
case Put(key, value, next) =>
case Put(key, value) =>
println(s"put($key, $value)")
kvs(key) = value
next
case g: Get[t, A] =>
println(s"get(${g.key})")
g.onResult(kvs(g.key).asInstanceOf[t])
case Delete(key, next) =>
()
case Get(key) =>
println(s"get($key)")
kvs.get(key).map(_.asInstanceOf[A])
case Delete(key) =>
println(s"delete($key)")
kvs.remove(key)
next
()
}
}
```
Expand All @@ -271,7 +215,7 @@ behavior, such as:
- a pseudo-random monad to support non-determinism
- and so on...

#### 6. Run your program
#### 5. Run your program

The final step is naturally running your program after compiling it.

Expand All @@ -293,7 +237,7 @@ recursive structure by:
This operation is called `Free.foldMap`:

```scala
final def foldMap[M[_]](f: S ~> M)(implicit S: Functor[S], M: Monad[M]): M[A] = ...
final def foldMap[M[_]](f: S ~> M)(M: Monad[M]): M[A] = ...
```

`M` must be a `Monad` to be flattenable (the famous monoid aspect
Expand All @@ -302,7 +246,7 @@ under `Monad`). As `Id` is a `Monad`, we can use `foldMap`.
To run your `Free` with previous `impureCompiler`:

```tut
val result: Int = program.foldMap(impureCompiler)
val result: Option[Int] = program.foldMap(impureCompiler)
```

An important aspect of `foldMap` is its **stack-safety**. It evaluates
Expand All @@ -317,35 +261,31 @@ data-intensive tasks, as well as infinite processes such as streams.
#### 7. Use a pure compiler (optional)

The previous examples used a effectful natural transformation. This
works, but you might prefer folding your `Free` in a "purer" way.

Using an immutable `Map`, it's impossible to write a natural
transformation using `foldMap` because you would need to know the
previous state of the `Map` and you don't have it. For this, you need
to use the lower level `fold` function and fold the `Free[_]` by
yourself:
works, but you might prefer folding your `Free` in a "purer" way. The
[State](state.html) data structure can be used to keep track of the program
state in an immutable map, avoiding mutation altogether.

```tut:silent
// Pure computation
def compilePure[A](program: KVStore[A], kvs: Map[String, A]): Map[String, A] =
program.fold(
_ => kvs,
{
case Put(key, value, next) => // help scalac past type erasure
compilePure[A](next, kvs + (key -> value.asInstanceOf[A]))
case g: Get[a, f] => // a bit more help for scalac
compilePure(g.onResult(kvs(g.key).asInstanceOf[a]), kvs)
case Delete(key, next) =>
compilePure(next, kvs - key)
})
import cats.state.State

type KVStoreState[A] = State[Map[String, Any], A]
val pureCompiler: KVStoreA ~> KVStoreState = new (KVStoreA ~> KVStoreState) {
def apply[A](fa: KVStoreA[A]): KVStoreState[A] =
fa match {
case Put(key, value) => State.modify(_.updated(key, value))
case Get(key) =>
State.pure(kvs.get(key).map(_.asInstanceOf[A]))
case Delete(key) => State.modify(_ - key)
}
}
```

(You can see that we are again running into some places where Scala's
support for pattern matching is limited by the JVM's type erasure, but
it's not too hard to get around.)

```tut
val result: Map[String, Int] = compilePure(program, Map.empty)
val result: (Map[String, Any], Option[Int]) = program.foldMap(pureCompiler).run(Map.empty).value
```

## Composing Free monads ADTs.
Expand Down