Skip to content

Commit

Permalink
edited lec 13
Browse files Browse the repository at this point in the history
  • Loading branch information
chris committed Sep 22, 2016
1 parent 3d083e0 commit 1daca93
Show file tree
Hide file tree
Showing 5 changed files with 1,291 additions and 542 deletions.
Binary file added slides/images/lecture13/free.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
146 changes: 108 additions & 38 deletions slides/lecture13.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,27 +126,30 @@ You can make a functor U from the category of monoids (where arrows are monoid h

---

The target of F is in the category Mon of monoids, where arrows are monoid homomorphisms, so we need a to show that a monoid homomorphism from [a] b can be described precisely by a function from a b.
The target of F is in the category Mon of monoids, where arrows are monoid homomorphisms, so we need a to show that a monoid homomorphism from [a] -> b can be described precisely by a function from a -> b.

In Haskell, we call the side of this that lives in Set (er, Hask, the category of Haskell types that we pretend is Set), just foldMap, which when specialized from Data.Foldable to Lists has type Monoid m => (a m) [a] m.
In Haskell, we call the side of this that lives in Set (er, Hask, the category of Haskell types that we pretend is Set), just foldMap, which when specialized from Data.Foldable to Lists has type Monoid m => (a -> m) -> [a] -> m.

---

You can compose all of this more directly by describing a list in these terms with:

newtype List a = List (forall b. Monoid b => (a -> b) -> b)

!haskell
interpretMonoid :: Monoid m => (a -> m) -> ([a] -> m)
interpretMonoid f [] = mempty
interpretMonoid f (a : as) = f a <> interpretMonoid f as

---


A general `Monoid[A]` essentially models a function of type `List[A] => A`.
<br />
<br />

We saw how the unit and associativity laws for monoids model the fact that we can fold left and right, and that the empty list doesn’t count.
<br />
<br />
We also defined a monoid as a category with one object.


---

Before, we represented `Monoid` as having a binary function and an identity element for that function.
Expand Down Expand Up @@ -195,6 +198,24 @@ Here’s a possible implementation:
foldRight(as, f))
}

---

#Example: Bank

This is an example of a catamorphism.

!haskell
data BankOp = Deposit Int | Withdraw Int
program = [Deposit 10, Withdraw 5, Withdraw 2, Deposit 3]

interpretOp :: BankOp -> Int
interpretOp (Deposit d) = d
interpretOp (Withdraw w) = -w

interpret = interpretMonoid interpretOp
interpret program --6


---

#List Monoid Composition
Expand Down Expand Up @@ -234,35 +255,78 @@ The Free Monad (data structure) is to the Monad (class) like the List (data stru
<br />
You know what a Monad is and that each Monad needs a specific (Monad-law abiding) implementation of either fmap + join + return or bind + return.

Free monads are the same idea. We take a functor, and give back a monad.

---

Free monads are the same idea. We take a functor, and give back a monad.
In fact the definition of a list looks a lot like the definition of a free monad:

In fact, since monads can be seen as monoids in the category of endofunctors, the definition of a list
!haskell
data [a] = [] | a : [a]
data Free f a = Return a | Suspend (f (Free f a))

data [a] = [] | a : [a]
---

This makes sense, since free monads can be seen as free monoids in the category of endofunctors.

looks a lot like the definition of free monads
Unlike List, which stores a list of values, Free stores a list of functors, wrapped around an initial value.

data Free f a = Return a | Suspend (f (Free f a))
Accordingly, the Functor and Monad instances of Free do nothing other than handing a given function down that list with fmap.

---

Let us assume you have a Functor (an implementation of fmap) but the rest depends on values and choices made at run-time, which means that you want to be able to use the Monad properties but want to choose the Monad-functions afterwards.
<br />
<br />
That can be done using the Free Monad (data structure), which wraps the Functor (type) in such a way so that the `join` is rather a stacking of those functors than a reduction.
---

Example: Toy

Slight variation on an example from [Gabriel Gonzalez](http://www.haskellforall.com/2012/06/you-could-have-invented-free-monads.html):

!scala
sealed trait Toy[+Next]
object Toy {
case class Output[Next](a: Char, next: Next) extends Toy[Next]
case class Bell[Next](next: Next) extends Toy[Next]
case class Done() extends Toy[Nothing]
def output[Next](a: Char, next: Next): Toy[Next] = Output(a, next)
def bell[Next](next: Next): Toy[Next] = Bell(next)
def done: Toy[Nothing] = Done()
}
import Toy._
output('A', done)
//res0: Toy[Toy[Nothing]] = Output(A,Done())
bell(output('A', done))
//res1: Toy[Toy[Toy[Nothing]]] = Bell(Output(A,Done()))

---

The real return and join you want to use, can now be given as parameters to the reduction function foldFree:
Note that the type changes every time a command is added.

foldFree :: Functor f => (a -> b) -> (f b -> b) -> Free f a -> b
foldFree return join :: Monad m => Free m a -> m a
case class Fix[F[_]](f: F[Fix[F]])
object Fix {
def fix(t: Toy[Fix[Toy]]) = Fix[Toy](t)
}

To explain the types, we can replace Functor f with Monad m and b with (m a):
import Fix._
fix(output('A', fix(done)))
//res176: Fix[Toy] = Fix(Output(A,Fix(Done())))
fix(bell(fix(output('A', fix(done)))))
//res177: Fix[Toy] = Fix(Bell(Fix(Output(A,Fix(Done())))))

foldFree :: Monad m => (a -> (m a)) -> (m (m a) -> (m a)) -> Free m a -> (m a)

data Fix f = Fix (f (Fix f))
data ListF a b = Nil | Cons a b
type List a = Fix (ListF a)


There's still a problem. This approach only works if you can use the Done constructor to terminate every chain of functors. Unfortunately, programmers don't often have the luxury of writing the entire program from start to finish. We often just want to write subroutines that can be called from within other programs and our Fix trick doesn't let us write a subroutine without terminating the entire program.


Let us assume you have a `Functor` but the rest depends on values and choices made at run-time.

This means that you want to be able to use the monad properties but want to choose the actual typeclass implementation afterwards.
<br />
<br />
That can be done using the Free Monad (data structure), which wraps the Functor (type) in such a way so that the `join` is rather a stacking of those functors than a reduction.

---

Expand All @@ -275,39 +339,45 @@ the first of these lets you "get into" your monad, and the second one gives you

---

The real `return` and `join` you want to use, can now be given as parameters to the reduction function `foldFree`:

!haskell
foldFree :: Functor f => (a -> b) -> (f b -> b) -> Free f a -> b
foldFree return join :: Monad m => Free m a -> m a

A Monad is something that "computes" when monadic context is collapsed by join :: m (m a) -> m a (recalling that >>= can be defined as (join .) . flip fmap).
To explain the types, we can replace Functor f with Monad m and b with (m a):

This is how Monads carry context through a sequential chain of computations: because at each point in the series, the context from the previous call is collapsed with the next.
foldFree :: Monad m => (a -> (m a)) -> (m (m a) -> (m a)) -> Free m a -> (m a)

---

We can take the same approach starting with the monad operations point and flatMap, but our task will be easier if we reformulate monads in terms of point, map, and join. Under this formulation a monad for a type F[_] has:
We can take the same approach starting with the monad operations `unit` and `flatMap`, but our task will be easier if we reformulate monads in terms of `unit`, `map`, and `join`.

Under this formulation a monad for a type `F[_]` has:

an operation point with type A => F[A];
an operation join with type F[F[A]] => F[A]; and
an operation map with type (F[A], A => B) => F[B].
* a unit with type `A => F[A]`;
* a join with type `F[F[A]] => F[A]`
* a map with type `(F[A], A => B) => F[B]`

---

From this list of operations we can start to create an abstract syntax tree. We start with the definition of Free.
From this list of operations we can start to create an abstract syntax tree. We start with the definition of `Free`.

sealed trait Free[F[_], A]
!scala
sealed trait Free[F[_], A]

---

We can directly convert point into a case Return (following the names I introduced in the introduction).
We can directly convert `unit` into a case `Return`:

final case class Return[F[_], A](a: A) extends Free[F, A]
!scala
final case class Return[F[_], A](a: A) extends Free[F, A]

---

We are going to convert join into a case Suspend.

What is the type of the value we store inside Suspend?

We might think it should store a value of type F[F[A]] but if we did this we wouldn’t be able to store, say, a Return inside the outer F.
We are going to convert `join` into a case `Suspend`, but what type of value should we store?

We might think to store a value of type `F[F[A]]`, but if we did then we wouldn’t be able to store, say, a `Return` inside the outer `F`.

---

Expand Down Expand Up @@ -345,14 +415,14 @@ data [a] = [] | a : [a]

To show this works, let’s implement the monad operations on this data type.

We’ll use the more familiar flatMap and point formulation, which is better suited to Scala, than the point, join, and map formulation above.
We’ll use the more familiar flatMap and unit formulation, which is better suited to Scala, than the unit, join, and map formulation above.

---

We can knock out point easily enough.
We can knock out unit easily enough.

object Free {
def point[F[_]](a: A): Free[F, A] = Return[F, A](a)
def unit[F[_]](a: A): Free[F, A] = Return[F, A](a)
}
Things get a bit trickier with flatMap, however. Since we know Free in an algebraic data type we can easily get the structural recursion skeleton.

Expand Down
110 changes: 39 additions & 71 deletions slides/lecture13a.md
Original file line number Diff line number Diff line change
@@ -1,53 +1,63 @@
#Lecture 13a: Interpreters

![](images/lecture3/gang-of-four-monads.png)

The interpreter is the über pattern of functional programming. Most large programs written in a functional style can be viewed as using this pattern. Amongst many reasons, interpreters allow us to handle effects and still keep desirable properties such as substitution.
---

The interpreter is the über pattern of functional programming.

Given the importance of interpreters it is not surprising there are many implementation strategies. In this blog post I want to discuss one of the main axes along which implementation strategies vary, which is how far we take reification of actions within the interpreter.
Intuition:

But first, a quick recap of the interpreter pattern, and the secret cheat code of functional programming, reification.
We treat the interpreter as part of the runtime.

What’s An Interpreter?
Our basic definition of an interpreter is anything that separates describing the computation from running it. That’s quite a mouthful, so let’s see a quick example. In Doodle, a library for vector graphics we are developing, we can describe a picture like so
We can talk about programs as values right up until we run them.
---

val picture1 = Image.circle(100)
When we’re describing a picture we can easily compose a new picture from existing pictures.
!haskell
interpretMonoid :: Monoid b => (a -> b) -> ([a] -> b)
interpretMonoid f [] = mempty
interpretMonoid f (a : as) = f a <> interpretMonoid f as

---

val picture2 = picture1 beside picture1
If we want to draw a picture, we call the draw method. Nothing appears on the screen until we call draw.
Most large programs written in a functional style can be viewed as using this pattern.

picture2.draw
The result of draw is Unit, so we cannot use this result to construct more pictures.
Amongst many reasons, interpreters allow us to handle effects and still keep desirable properties such as substitution.

This illustrates the essential features of the interpreter pattern: describing a picture is the “describing the computation” part, and calling draw is the “running the computation” part.
---

Why Do We Use Interpreters?
One important reason for functional programmers liking the interpreter pattern is how it allows us to deal with effects. Effects are problematic, because they break substitution. Substitution allows easy reasoning about code, so functional programmers strive hard to maintain it. At some point you have to have effects—if not, the programm will not do anything useful. The secret to allowing effects is to delay them until some point in the program where we don’t care about substitution anymore. For example, in a web service we can delay effects till we’ve constructed the program to create the response, and then run that program, causing our database effects and so on to occur. In Doodle we delay effects—drawing—until we’ve fully described the picture we want to draw. If you’re familiar with computer graphics, Doodle is essentially constructing a scene graph.
One important reason for functional programmers liking the interpreter pattern is how it allows us to deal with effects. Effects are problematic, because they break substitution. Substitution allows easy reasoning about code, so functional programmers strive hard to maintain it. At some point you have to have effects—if not, the program will not do anything useful. The secret to allowing effects is to delay them until some point in the program where we don’t care about substitution anymore. For example, in a web service we can delay effects till we’ve constructed the program to create the response, and then run that program, causing our database effects and so on to occur. In Doodle we delay effects—drawing—until we’ve fully described the picture we want to draw. If you’re familiar with computer graphics, Doodle is essentially constructing a scene graph.

---

#Example: Random

The Random monad allows us to separate describing how to generate random data from actually introducing randomness.

Reification
Reification is an integral part of the interpreter pattern. Reification means to make the abstract concrete. In the context of interpreters this means to turn an action into data. For example, in the context of Doodle when we call Image.circle(100) this turns into a case class rather than drawing something on the screen. Reification is an essential part of the interpreter pattern as we need to delay running the program for the pattern to work, as described above, and the only way to do this is to create a data structure of some kind.
Randomness is an effect (a function that generates a random value breaks substitution, as it returns a different value each time it is called) and so we can use the Random monad to control this effect.

Opaque and Transparent Interpreters
With the background out of the way, let’s move to the main content of this post: talking about different implementation strategies. I’m going to use as an example of the Random monad. The Random monad allows us to separate describing how to generate random data from actually introducing randomness. Randomness is an effect (a function that generates a random value breaks substitution, as it returns a different value each time it is called) and so we can use the Random monad to control this effect. The Random monad is very simple to implement and make a good case study of different implementation techniques.
---

Let’s look at two different implementation strategies. For bonus points I’ve implemented a cats monad instance for both, but this has no bearing on the point I’m making.
Let’s look at two different implementation strategies.

The first strategy is to reify the methods to an algebraic data type.

import cats.Monad

import cats.Monad
sealed trait AlgebraicDataType[A] extends Product with Serializable {
def run(rng: scala.util.Random = scala.util.Random): A =
this match {
case Primitive(sample) => sample(rng)
case FlatMap(fa, f) => f(fa.run(rng)).run(rng)
}

def flatMap[B](f: A => AlgebraicDataType[B]): AlgebraicDataType[B] =
FlatMap(this, f)
def flatMap[B](f: A => AlgebraicDataType[B]): AlgebraicDataType[B] = FlatMap(this, f)

def map[B](f: A => B): AlgebraicDataType[B] =
FlatMap(this, (a: A) => AlgebraicDataType.always(f(a)))
}


object AlgebraicDataType {
def always[A](a: A): AlgebraicDataType[A] =
Primitive(rng => a)
Expand Down Expand Up @@ -112,58 +122,13 @@ The transparent interpreter is more modular than the opaque one. With a transpar

In summary, transparent interpreters trade performance for flexibility. I usually find that flexibility is the right choice.

It’s worth noting there is another implementation technique (there is always another way in Scala.) This is to use anonymous classes, as show belown in AnonymousTrait. This is still an opaque implementation, just one that is much more verbose than Lambda above.

import cats.Monad

sealed trait AnonymousTrait[A] { self =>
def run(rng: scala.util.Random = scala.util.Random): A

def flatMap[B](f: A => AnonymousTrait[B]): AnonymousTrait[B] =
new AnonymousTrait[B] {
def run(rng: scala.util.Random): B =
f(self.run(rng)).run(rng)
}

def map[B](f: A => B): AnonymousTrait[B] =
new AnonymousTrait[B] {
def run(rng: scala.util.Random): B =
f(self.run(rng))
}
}
object AnonymousTrait {
def always[A](a: A): AnonymousTrait[A] =
new AnonymousTrait[A] {
def run(rng: scala.util.Random): A =
a
}

def int: AnonymousTrait[Int] =
new AnonymousTrait[Int] {
def run(rng: scala.util.Random): Int =
rng.nextInt()
}

def double: AnonymousTrait[Double] =
new AnonymousTrait[Double] {
def run(rng: scala.util.Random): Double =
rng.nextDouble()
}

implicit object randomInstance extends Monad[AnonymousTrait] {
def flatMap[A, B](fa: AnonymousTrait[A])(f: (A) ⇒ AnonymousTrait[B]): AnonymousTrait[B] =
fa.flatMap(f)
---

def pure[A](x: A): AnonymousTrait[A] =
AnonymousTrait.always(x)
}
}
I’ve also seen this technique used with an unsealed trait. I think this is bad practice. An unsealed trait allows random changes to the semantics by overriding, and doesn’t indicate to the user how they should use the class. Unsealed traits should generally be used for type classes, in my opinion.
#Modular Interpreters

Modular Interpreters
One way we can take advantage of the modularity offered by transparent interpreters is by capturing common patterns in reusable classes. This is exactly what the free monad does. Here’s an example of the Random monad using the free monad implementation in Cats. As you can see, we don’t have to write a great deal of code, and we benefit from an optimised and stack-safe implementation.

object RandomM {

import cats.free.Free
import cats.{Comonad,Monad}

Expand All @@ -183,7 +148,10 @@ object RandomM {
override def map[A, B](fa: Primitive[A])(f: (A) ⇒ B): Primitive[B] =
Primitive(rng => f(fa.sample(rng)))
}
}


---

Conclusions
The transparency tradeoff is a major design decision in creating an interpreter. Over time I feel that as a community we’re moving towards more transparent representations, particularly as techniques like the free monad become more widely known.

Expand Down
Loading

0 comments on commit 1daca93

Please sign in to comment.