POST-FREE:
LIFE AFTER
FREE MONADSJOHN A. DE GOES — @JDEGOES
OUTLINE
> Free What?
> Live Free...
> ...Or Die Hard
> Exploration of Solutions
> Hacks for Free
> Reconstructing Free
> Fixing Free
> Closing Thoughts
FREE WHAT?
REINVENTING FREE: PURE EFFECTS
data ConsoleIO
= WriteLine String ConsoleIO
| ReadLine (String -> ConsoleIO)
| End
sealed trait ConsoleIO
final case class WriteLine(line: String, then: ConsoleIO) extends ConsoleIO
final case class ReadLine(process: String => ConsoleIO) extends ConsoleIO
final case object End extends ConsoleIO
FREE WHAT?
REINVENTING FREE: PURE EFFECTS W/RETURNS
data ConsoleIO a
= WriteLine String (ConsoleIO a)
| ReadLine (String -> ConsoleIO a)
| EndWith a
sealed trait ConsoleIO[A]
final case class WriteLine[A](line: String, then: ConsoleIO[A])
extends ConsoleIO[A]
final case class ReadLine[A](process: String => ConsoleIO[A])
extends ConsoleIO[A]
final case class EndWith[A](value: A) extends ConsoleIO[A]
FREE WHAT?
REINVENTING FREE: PURE EFFECTS W/MAP (PURESCRIPT)
data ConsoleIO a
= WriteLine String (ConsoleIO a)
| ReadLine (String -> ConsoleIO a)
| EndWith a
| Map (forall z. (forall a0. ConsoleIO a0 -> (a0 -> a) -> z) -> z)
FREE WHAT?
REINVENTING FREE: PURE EFFECTS W/MAP (SCALA)
sealed trait ConsoleIO[A] {
def map[B](f: A => B): Console[B] = Map(this, f)
}
final case class WriteLine[A](line: String, then: ConsoleIO[A])
extends ConsoleIO[A]
final case class ReadLine[A](process: String => ConsoleIO[A])
extends ConsoleIO[A]
final case class EndWith[A](value: A) extends ConsoleIO[A]
final case class Map[A0, A](v: ConsoleIO[A0], f: A0 => A)
extends ConsoleIO[A]
FREE WHAT?
REINVENTING FREE: SIMPLER EFFECTS W/BIND (PURESCRIPT)
data ConsoleIO a
= WriteLine String a
| ReadLine (String -> a)
| Pure a
| Chain (forall z. (forall a0.
Console a0 -> (a0 -> ConsoleIO a) -> z) -> z)
FREE WHAT?
REINVENTING FREE: SIMPLER EFFECTS W/BIND (SCALA)
sealed trait ConsoleIO[A] {
def map[B](f: A => B): ConsoleIO[B] = flatMap(a => Pure[B](f(a)))
def flatMap[B](f: A => ConsoleIO[B]): ConsoleIO[B] = Chain(this, f)
}
final case class WriteLine(line: String) extends ConsoleIO[Unit]
final case class ReadLine() extends ConsoleIO[String]
final case class Pure[A](value: A) extends ConsoleIO[A]
final case class Chain[A0, A](v: ConsoleIO[A0], f: A0 => ConsoleIO[A])
extends ConsoleIO[A]
FREE WHAT?
REINVENTING FREE: SIMPLER EFFECTS W/BIND (PURESCRIPT)
data ConsoleIOF a
= WriteLine String a
| ReadLine (String -> a)
data Free f a
= Pure a
| Effect (f a)
| Chain (forall z. (
forall a0. Free f a0 -> (a0 -> Free f a) -> z) -> z)
type ConsoleIO = Free ConsoleIOF
FREE WHAT?
REINVENTING FREE: SIMPLER EFFECTS W/BIND (SCALA)
sealed trait ConsoleIOF[A]
final case class WriteLine(line: String) extends ConsoleIOF[Unit]
final case class ReadLine() extends ConsoleIOF[String]
sealed trait Free[F[_], A]
final case class Pure[F[_], A](value: A) extends Free[F, A]
final case class Effect[F[_], A](effect: F[A]) extends Free[F, A]
final case class Chain[F[_], A0, A](v: Free[A0], f: A0 => Free[A])
extends Free[F, A]
type ConsoleIO[A] = Free[ConsoleIOF, A]
FREE WHAT?
CLASSIC DEFINITION OF FREE
data Free f a = Point a | Join (f (Free f a))
sealed trait Free[F[_], A]
final case class Point[F[_], A](value: A) extends Free[F, A]
final case class Join[F[_], A](value: F[Free[F, A]]) extends Free[F, A]
FREE WHAT?
INTERPRETATION OF FREE
Free f a
Free[F, A]
^ ^ ^
|  ------- The value produced by the program
| 
|  The effects of the program
|
A program that halts, runs
forever, or produces an A
FREE WHAT?
EXAMPLE: EFFECTS
data ConsoleIO a
= ReadLine (String -> a)
| WriteLine a String
sealed trait ConsoleIO[A]
final case class ReadLine[A](next: String => A) extends ConsoleIO[A]
final case class WriteLine[A](next: A, line: String) extends ConsoleIO[A]
FREE WHAT?
EXAMPLE: HELPERS
readLine :: Free ConsoleIO String
readLine = liftF (ReadLine id)
writeLine :: String -> Free ConsoleIO Unit
writeLine = liftF <<< WriteLine unit
def readLine: Free[ConsoleIO, String] =
Free.liftF(ReadLine[String](identity))
def writeLine(line: String): Free[ConsoleIO, Unit] =
Free.liftF(WriteLine[Unit]((), line))
FREE WHAT?
EXAMPLE: PROGRAM
program = do
writeLine "What is your name?"
n <- readLine
writeLine ("Hello, " <> n <> "!")
return unit
def program: Free[ConsoleIO, Unit] =
for {
_ <- writeLine("What is your name?")
n <- readLine
_ <- writeLine("Hello, " + n + "!")
} yield ()
FREE WHAT?
COMPOSITION: FILEIO
data FileIO a
= ReadFile (Bytes -> a) String
| WriteFile a String Bytes
sealed trait FileIO[A]
final case class ReadFile[A](next: Bytes => A, name: String)
extends FileIO[A]
final case class WriteFile[A](next: A, name: String, file: Bytes)
extends FileIO[A]
FREE WHAT?
COMPOSITION: COPRODUCT
data Coproduct f g a = Left (f a) | Right (g a)
sealed trait Coproduct[F[_], G[_], A]
final case class Left[F[_], G[_], A](value: F[A])
extends Coproduct[F, G, A]
final case class Right[F[_], G[_], A](value: G[A])
extends Coproduct[F, G, A]
FREE WHAT?
COMPOSITION: PROGRAM
type Program = Free (Coproduct FileIO ConsoleIO)
type Program[A] = Free[Coproduct[FileIO, ConsoleIO, ?], A]
LIVE FREE...
TYPE-SAFE MOCKING
mockSpec :: MockSpec ConsoleIO
mockSpec = do
expectWrite _WriteLine (assertEquals "What is your name?")
expectRead _ReadLine "World"
expectWrite _WriteLine (assertEquals "Hello, World!")
def mockSpec: MockSpec[ConsoleIO] =
for {
_ <- expectWrite(_WriteLine, assertEquals("What is your name?"))
_ <- expectRead(_ReadLine, "World")
_ <- expectWrite(_WriteLine, assertEquals("Hello World"))
} yield ()
LIVE FREE...
RUNTIME OPTIMIZATION (PURESCRIPT)
data Parser a
= ParseChar (Char -> a)
| ParseString (String -> a)
| Fail String
optimize :: FreeAp Parser ~> FreeAp Parser
optimize = ...
LIVE FREE...
RUNTIME OPTIMIZATION (SCALA)
sealed trait Parser[A]
final case class ParseChar[A](next: Char => A) extends Parser[A]
final case class ParseString[A](next: String => A) extends Parser[A]
final case class Fail[A](error: String) extends Parser[A]
def optimize: FreeAp[Parser, ?] ~> FreeAp[Parser, ?] = ???
LIVE FREE...
ASPECT-ORIENTED PROGRAMMING (PURESCRIPT)
log line = liftF (Left (WriteLine unit line))
weaveLogging :: FileIO ~> Free (Coproduct ConsoleIO FileIO)
weaveLogging (ReadFile next name) = do
log $ "Reading file: " <> name
bytes <- liftF (Right (ReadFile id name))
log $ "File contents: " <> show bytes
return (next bytes)
weaveLogging (WriteFile next name bytes) = ...
program :: Free FileIO Unit
program = ...
program' :: Free (Coproduct ConsoleIO FileIO) Unit
program' = foldFree weaveLogging program
LIVE FREE...
ASPECT-ORIENTED PROGRAMMING (SCALA)
def weaveLogging: FileIO ~> Free[Coproduct[ConsoleIO, FileIO, ?], ?]
= new NaturalTransformation[FileIO, Coproduct[ConsoleIO, FileIO, ?]] {
def log(line: String) =
Free.liftF(Left[ConsoleIO, FileIO, Unit](WriteLine(unit, line)))
def apply[A](fa: FileIO[A]): Coproduct[ConsoleIO, FileIO, A] =
fa match {
case (ReadFile(next, name)) =>
for {
_ <- log("Reading file: " + name)
bytes <- liftF(Right[ConsoleIO, FileIO, Bytes](ReadFile(id, name))
_ <- log("File contents: " + bytes)
} yield next(bytes)
case (WriteFile(next, name, bytes)) =>
???
}
}
...OR DIE HARD
THE FREE MONAD IS ONLY A FREE MONAD
> Parallelism
> Failure
> Alternatives
> Nondeterminism
> ...And all other abstractions with more structure than a
monad.
...OR DIE HARD
USE CASE #1: CONCURRENCY
loadModel = do
token <- authenticate
sequential $
Model <$> parallel (get "/products/popular/" token)
<*> parallel (get "/categories/all" token)
...OR DIE HARD
USE CASE #2: NONDETERMINISM
data UI a
= Click (ClickEvent -> a)
| KeyPress (KeyEvent -> a)
| GetClass (String -> a)
| SetClass String a
| ...
doubleClick btn =
guard ((e1 e2 -> (e2.ts - e1.ts) < 200) <$> click btn <*> click btn)
toggleOnDoubleClick btn =
doubleClick btn *> toggleClass "toggled" btn
toggleFirst = foldl (e m -> m <|> toggleOnDoubleClick e) empty buttons
...OR DIE HARD
USE CASE #3: FAILURE
handleError (readConfig specifiedDir) (const $ readConfig defaultDir)
handleError(readConfig(specifiedDir),
Function.const(readConfig(defaultDir)))
HACKS FOR FREE
HACKING PARALLELISM: TYPE & EFFECT
type SeqPar f = Free (FreeAp f)
liftFA :: forall f. f ~> SeqPar f
liftFA fa = liftF (liftFreeAp fa)
type SeqPar[F[_], A] = Free[FreeAp[F, ?], A]
def liftFA[F[_], A](fa: F[A]): SeqPar[F, A] =
Free.liftF[FreeAp[F, ?], A](FreeAp.lift(fa))
HACKS FOR FREE
HACKING PARALLELISM: LIFTING PAR & SEQ
liftSeq :: forall f a. Free f a -> SeqPar f a
liftSeq = foldFree liftFA
liftPar :: forall f a. FreeAp f a -> SeqPar f a
liftPar = liftF
def liftSeq[F[_], A](freefa: Free[F, A]): SeqPar[F, A] = {
implicit val m: Monad[SeqPar[F, ?]] = Free.freeMonad[FreeAp[F, ?]]
freefa.foldMap[SeqPar[F, ?]](new NaturalTransformation[F, SeqPar[F, ?]] {
def apply[A](fa: F[A]): SeqPar[F, A] = liftFA(fa)
})
}
def liftPar[F[_], A](freeap: FreeAp[F, A]): SeqPar[F, A] =
Free.liftF[FreeAp[F, ?], A](freeap)
HACKS FOR FREE
HACKING PARALLELISM: OPTIMIZATION
type ParInterpreter f g = FreeAp f ~> g
type ParOptimizer f g = ParInterpreter f (SeqPar g)
optimize :: forall f g a. (FreeAp f ~> SeqPar g) -> SeqPar f a -> SeqPar g a
optimize = foldFree
type ParInterpreter[F[_], G[_]] = FreeAp[F, ?] ~> G
type ParOptimizer[F[_], G[_]] = ParInterpreter[F, SeqPar[G, ?]]
def optimize[F[_], G[_], A](nt: FreeAp[F, ?] ~> SeqPar[G, ?], p: SeqPar[F, A]): SeqPar[G, A] = {
implicit val m: Monad[SeqPar[G, ?]] = Free.freeMonad[FreeAp[G, ?]]
p.foldMap[SeqPar[G, ?]](nt)
}
def parOptimize[F[_], G[_], A](nt: FreeAp[F, ?] ~> FreeAp[G, ?], p: SeqPar[F, A]): SeqPar[G, A] =
optimize(new NaturalTransformation[FreeAp[F, ?], SeqPar[G, ?]] {
def apply[A](freeap: FreeAp[F, A]): SeqPar[G, A] =
liftPar(nt(freeap))
}, p)
HACKS FOR FREE
HACKING NONDETERMINISM: TYPE
data FreeAlt e f a = FreeAlt (Free (Alt e f) a)
data Alt e f a
= Failure e
| FirstSuccess (FreeAlt e f a) (FreeAlt e f a)
| Effect (f a)
final case class FreeAlt[E, F[_], A](run: Free[Alt[E, F, ?], A])
sealed trait Alt[E, F[_], A]
final case class Failure[E, F[_], A](error: E) extends Alt[E, F, A]
final case class FirstSuccess[E, F[_], A](first: FreeAlt[E, F, A],
second: FreeAlt[E, F, A], merge: (E, E) => E) extends Alt[E, F, A]
final case class Effect[E, F[_], A](effect: F[A]) extends Alt[E, F, A]
HACKS FOR FREE
HACKING NONDETERMINISM: INSTANCES
implicit def FreeAltMonadPlus[E: Monoid, F[_]]:
MonadPlus[FreeAlt[E, F, ?]] = new MonadPlus[FreeAlt[E, F, ?]] {
implicit val m: Monad[Free[Alt[E, F, ?], ?]] =
Free.freeMonad[Alt[E, F, ?]]
def point[A](a: => A): FreeAlt[E, F, A] =
FreeAlt[E, F, A](m.point(a))
def bind[A, B](fa: FreeAlt[E, F, A])(f: A => FreeAlt[E, F, B]):
FreeAlt[E, F, B] =
FreeAlt[E, F, B](m.bind(fa.run)(f.map(_.run)))
def plus[A](a: FreeAlt[E, F, A], b: => FreeAlt[E, F, A]): FreeAlt[E, F, A] =
FreeAlt(Free.liftF[Alt[E, F, ?], A](
FirstSuccess[E, F, A](a, b, Monoid[E].append(_, _))))
def empty[A]: FreeAlt[E, F, A] =
FreeAlt(Free.liftF[Alt[E, F, ?], A](Failure[E, F, A](Monoid[E].zero)))
}
HACKS FOR FREE
HACKING NONDETERMINISM: IMPROVING COMPOSABILITY
(PURESCRIPT)
data FreeAlt e f a = Free (Alt FreeAlt e f) a
data Alt t e f a
= Failure e
| FirstSuccess (t e f a) (t e f a)
| Effect (f a)
HACKS FOR FREE
HACKING NONDETERMINISM: IMPROVING COMPOSABILITY (SCALA)
final case class FreeAlt[E, F[_], A](run: Free[Alt[FreeAlt, E, F, ?], A])
sealed trait Alt[T[_, _[_], _], E, F[_], A]
final case class Failure[E, F[_], A](error: E) extends Alt[E, F, A]
final case class FirstSuccess[E, F[_], A](first: T[E, F, A],
second: T[E, F, A], merge: (E, E) => E) extends Alt[E, F, A]
final case class Effect[E, F[_], A](effect: F[A]) extends Alt[E, F, A]
HACKS FOR FREE
THE PROBLEMS WITH HACKING
> Composes poorly, and at great cost to performance, usability
data MyFree f a
= MyFree (Free (
Alt MyFree (
Parallel MyFree (
Race MyFree f))) a)
final case class MyFree[F[_], A](
run: Free[Alt[MyFree, Parallel[MyFree, Race[MyFree, F, ?], ?], ?], A])
> Blurs machinery & effects
> Where is effect? It's nested inside n-levels of machinery.
HACKS FOR FREE
WISH LIST
> Improve performance
> Improve usability
> Cleanly separate effects and machinery
RECONSTRUCTING FREE
TYPE DEFINITION (PURESCRIPT)
data FreeStar e f a
^ ^ ^
| | |
Error| Return Value
|
Effect
RECONSTRUCTING FREE
TYPE DEFINITION (SCALA)
sealed trait FreeStar[E, F[_], A]
^ ^ ^
| | |
Error | Return Value
|
Effect
RECONSTRUCTING FREE
TYPE DEFINITION (PURESCRIPT)
data FreeStar e f a
= Pure a
| Effect (f a)
| Sequence (forall z.
(forall a0.
FreeStar e f a0 -> (a0 -> FreeStar e f a) -> z) -> z)
| Parallel (forall z.
(forall l r.
FreeStar e f l -> FreeStar e f r -> (l -> r -> a) -> z) -> z)
| Failure e
| Recover (FreeStar e f a) (e -> FreeStar e f a)
| FirstSuccess (FreeStar e f a) (FreeStar e f a)
RECONSTRUCTING FREE
PURITY
final case class Pure[E, F[_], A](a: A) extends FreeStar[E, F, A]
RECONSTRUCTING FREE
EFFECTS
final case class Effect[E, F[_], A](fa: F[A]) extends FreeStar[E, F, A]
RECONSTRUCTING FREE
SEQUENCING
sealed trait Sequence[E, F[_], A] extends FreeStar[E, F, A] {
type A0
def a: FreeStar[E, F, A0]
def f: A0 => FreeStar[E, F, A]
}
RECONSTRUCTING FREE
PARALLELISM
sealed trait Parallel[E, F[_], A] extends FreeStar[E, F, A] {
type B
type C
def left: FreeStar[E, F, B]
def right: FreeStar[E, F, C]
def join: (B, C) => A
}
RECONSTRUCTING FREE
FAILURE
final case class Failure[E, F[_], A](error: E) extends FreeStar[E, F, A]
final case class Recover[E, F[_], A](
value: FreeStar[E, F, A],
f: E => FreeStar[E, F, A]) extends FreeStar[E, F, A]
RECONSTRUCTING FREE
NONDETERMINISM
final case class FirstSuccess[E, F[_], A](
first: FreeStar[E, F, A],
second: FreeStar[E, F, A]) extends FreeStar[E, F, A]
RECONSTRUCTING FREE
INSTANCES
implicit def FreeStarMonadPlus[E: Monoid, F[_]] =
new MonadPlus[FreeStar[E, F, ?]] with MonadError[FreeStar[E, F, ?], E] {
def point[A](a: => A): FreeStar[E, F, A] =
Pure[E, F, A](a)
def bind[A, B](fa: FreeStar[E, F, A])(f: A => FreeStar[E, F, B]): FreeStar[E, F, B] =
Sequence[A, E, F, B](fa, f)
def plus[A](a: FreeStar[E, F, A], b: => FreeStar[E, F, A]): FreeStar[E, F, A] =
FirstSuccess(a, b)
def empty[A]: FreeStar[E, F, A] =
Failure[E, F, A](Monoid[E].zero)
def raiseError[A](e: E): FreeStar[E, F, A] = Failure(e)
def handleError[A](fa: FreeStar[E, F, A])(f: E => FreeStar[E, F, A]): FreeStar[E, F, A] =
Recover(fa, f)
}
RECONSTRUCTING FREE
THE PROBLEMS WITH RECONSTRUCTION
> Whole program has access to all features
> Constraints (on features) liberate (interpreters)
> Liberties (on features) constrain (interpreters)
> Cannot express one feature in terms of others
> Must interpret all features at once
RECONSTRUCTING FREE
WISH LIST
> Fine-grained features — pay for what you use
> Compositional features
> Compositional interpreters
FIXING FREE
DETOUR: RECURSIVE EXPR
data Expr = Lit Int | Add Expr Expr
sealed trait Expr
final case class Lit(value: Int) extends Expr
final case class Add(left: Expr, right: Expr) extends Expr
FIXING FREE
DETOUR: FIXED EXPR
data Expr a = Lit Int | Add a a
data Fixed f = Fixed (f (Fixed f))
type RecursiveExpr = Fixed Expr
sealed trait Expr[A]
final case class Lit[A](value: Int) extends Expr[A]
final case class Add[A](left: A, right: A) extends Expr[A]
final case class Fixed[F[_]](unfix: F[Fixed[F]])
type RecursiveExpr = Fixed[Expr]
FIXING FREE
DETOUR: RECURSIVE LIST
data List a = Empty | Cons a (List a)
sealed trait List[A]
final case class Empty[A]() extends List[A]
final case class Cons[A](head: A, tail: List[A]) extends List[A]
FIXING FREE
DETOUR: FIXED LIST
data ListF z a = Empty | Cons a (z a)
data Fixed t a = Fixed (t (Fixed t) a)
type List = Fixed LiftF
sealed trait ListF[Z[_], A]
final case class Empty[Z[_], A]() extends ListF[Z, A]
final case class Cons[Z[_], A](head: A, tail: Z[A]) extends ListF[Z, A]
final case class Fixed[T[_[_], _], A](unfix: T[Fixed[T, ?], A])
type List[A] = Fixed[ListF, A]
FIXING FREE
DETOUR: FIXED LIST — EMPTY, CONS, UNCONS
empty :: List a
empty = Fixed Empty
cons :: a -> List a -> List a
cons a as = Fixed (Cons a as)
uncons :: List a -> Maybe (Tuple a List a)
uncons (Fixed Empty) = Nothing
uncons (Fixed (Cons a as)) = Just (Tuple a as)
def empty[A]: List[A] =
Fixed[ListF, A](Empty[List, A](): ListF[List, A])
def cons[A](a: A, as: List[A]): List[A] =
Fixed[ListF, A](Cons[List, A](a, as): ListF[List, A])
def uncons[A](as: List[A]): Option[(A, List[A])] = as match {
case Fixed(l) => (l : ListF[List, A]) match {
case x : Empty[List, A] => None
case x : Cons[List, A] => Some((x.head, x.tail))
}
}
FIXING FREE
DETOUR: FIXED LIST — COMPOSABLE TERMS (PURESCRIPT)
data Empty z a = Empty
data Cons z a = Cons a (z a)
data Concat z a = Concat (z a) (z a)
data Coproduct t1 t2 z a
= CLeft (t1 z a)
| CRight (t2 z a)
data Fixed t a = Fixed (t (Fixed t) a)
type ConsOrCat = Coproduct Cons Concat
type EmptyOrConsOrCat = Coproduct Empty ConsOrCat
type List a = Fixed EmptyOrConsOrCat a
FIXING FREE
DETOUR: FIXED LIST — COMPOSABLE TERMS (SCALA)
final case class Empty[Z[_], A]()
final case class Cons[Z[_], A](head: A, tail: Z[A])
final case class Concat[Z[_], A](first: Z[A], last: Z[A])
sealed trait Coproduct[T1[_[_], _], T2[_[_], _], Z[_], A]
case class CLeft[T1[_[_], _], T2[_[_], _], Z[_], A](value: T1[Z, A])
extends Coproduct[T1, T2, Z, A]
case class CRight[T1[_[_], _], T2[_[_], _], Z[_], A](value: T2[Z, A])
extends Coproduct[T1, T2, Z, A]
final case class Fixed[T[_[_], _], A](unfix: T[Fixed[T, ?], A])
type ConsOrCat[Z[_], A] = Coproduct[Cons, Concat, Z, A]
type EmptyOrConsOrCat[Z[_], A] = Coproduct[Empty, ConsOrCat, Z, A]
type List[A] = Fixed[EmptyOrConsOrCat, A]
FIXING FREE
DETOUR: FIXED LIST — COMPOSABLE TERMS (PURESCRIPT)
empty :: forall a. List a
empty = Fixed (CLeft Empty)
cons :: forall a. a -> List a -> List a
cons a as = Fixed <<< CRight <<< CLeft $ Cons a as
concat :: forall a. List a -> List a -> List a
concat a1 a2 = Fixed <<< CRight <<< CRight $ Concat a1 a2
uncons :: forall a. List a -> Maybe (Tuple a (List a))
uncons (Fixed f) = case f of
CLeft Empty -> Nothing
CRight (CLeft (Cons a as)) -> Just (Tuple a as)
CRight (CRight (Concat a1 a2)) -> case uncons a1 of
Nothing -> Nothing
Just (Tuple a as) -> Just (Tuple a (concat as a2))
FIXING FREE
DETOUR: FIXED LIST — COMPOSABLE TERMS (SCALA)
def empty[A]: List[A] =
Fixed[EmptyOrConsOrCat, A](
CLeft[Empty, ConsOrCat, List, A](Empty[List, A]()))
def cons[A](a: A, as: List[A]): List[A] =
Fixed[EmptyOrConsOrCat, A](
CRight[Empty, ConsOrCat, List, A](
CLeft[Cons, Concat, List, A](Cons[List, A](a, as))))
def concat[A](first: List[A], last: List[A]): List[A] =
Fixed[EmptyOrConsOrCat, A](
CRight[Empty, ConsOrCat, List, A](
CRight[Cons, Concat, List, A](Concat[List, A](first, last))))
def uncons[A](as: List[A]): Option[(A, List[A])] = as.unfix match {
case _ : CLeft[Empty, ConsOrCat, List, A] => None
case v : CRight[Empty, ConsOrCat, List, A] => v.value match {
case v : CLeft[Cons, Concat, List, A] => Some((v.value.head, v.value.tail))
case v : CRight[Cons, Concat, List, A] => uncons(v.value.first) match {
case None => uncons(v.value.last)
case Some((a, as)) => Some((a, concat(as, v.value.last)))
}
}
}
FIXING FREE
BASIC TERM DEFINITION
A program that halts, runs
forever, or produces an A
^
|
|
t z e a
T[Z[_], E[_], A]
^ ^ ^
| | |
| | |
Self | |
Effect |
Return
Kind: (* -> *) -> (* -> *) -> * -> *
FIXING FREE
EXTENDED TERM DEFINITION
A program that halts, errors
with an E, runs forever, or
produces an A
^
|
|
t e z e a
T[E, Z[_], E[_], A]
^ ^ ^ ^
| | | |
| | | |
| Self | |
| Effect |
| Return
Error
Kind: * -> (* -> *) -> (* -> *) -> * -> *
FIXING FREE
PURITY
data Pure e z f a = Pure a
final case class Pure[E, Z[_], F[_], A](a: A)
FIXING FREE
EFFECTS
data Effect e z f a = Effect (f a)
final case class Effect[E, Z[_], F[_], A](fa: F[A])
FIXING FREE
SEQUENCE
data Sequence e z f a
= Sequence (forall x. (forall a0. z a0 -> (a0 -> z a) -> x) -> x)
trait Sequence[E, Z[_], F[_], A] {
type A0
def a: Z[A0]
def f: A0 => Z[A]
}
FIXING FREE
PARALLEL
data Parallel e z f a
= Parallel (
forall w. (
forall l r. z l -> z r -> (l -> r -> a) -> w) -> w)
trait Parallel[E, Z[_], F[_], A] {
type B
type C
def left: Z[B]
def right: Z[C]
def join: (B, C) => A
}
FIXING FREE
FAILURE
data Failure e z f a = Failure e
data Recover e z f a = Recover (z a) (e -> z a)
case class Failure[E, Z[_], F[_], A](error: E)
case class Recover[E, Z[_], F[_], A](value: Z[A], f: E => Z[A])
FIXING FREE
ORDERED ALTERNATE
data FirstSuccess e z f a = FirstSuccess (z a) (z a)
case class FirstSuccess[E, Z[_], F[_], A](first: Z[A], second: Z[A])
FIXING FREE
TERM COMPOSITION
data EitherF t1 t2 e z f a
= LeftF (t1 e z f a)
| RightF (t1 e z f a)
sealed trait EitherF[T1[_, _[_], _[_], _], T2[_, _[_], _[_], _], E, Z[_], F[_], A]
final case class LeftF[T1[_, _[_], _[_], _], T2[_, _[_], _[_], _], E, Z[_], F[_], A](
value: T1[E, Z, F, A]) extends EitherF[T1, T2, E, Z, F, A]
final case class RightF[T1[_, _[_], _[_], _], T2[_, _[_], _[_], _], E, Z[_], F[_], A](
value: T2[E, Z, F, A]) extends EitherF[T1, T2, E, Z, F, A]
FIXING FREE
FIXING
data Fixed t e f a = Fixed (t e (Fixed t e f) f a)
final case class Fixed[T[_, _[_], _[_], _], E, F[_], A](
unfix: T[E, Fixed[T, E, F, ?], F, A]
)
FIXING FREE
THE PROBLEMS WITH FIXING
> Straining language capabilities
> "Benign" boilerplate
> But...type-level or macro machinery to the rescue?
FIXING FREE
WISH LIST
> Language-level support for positively & negatively
constrained, heterogeneous sets (unions)
> Recursion as a computational feature
> i.e. A NEW PROGRAMMING LANGUAGE
CLOSING THOUGHTS
WHERE WE ARE TODAY
> "Post-free" is about reifying the structure of computation
> By constraining the structure of subprograms, we
liberate interpretation of them
> We can do "post-free" now using a variety of
techniques
CLOSING THOUGHTS
WHERE WE COULD GO TOMORROW
> Post-free points to a world where programs are defined by:
> (a) Defining computational features in terms of others
> (b) Defining program effects in terms of others (onion
architecture)
> (c) Type-safe weaving, introspection, mocking, optimization, etc.
> (d) Purely denotational semantics for programs
THANK YOUFOLLOW ME ON TWITTER AT @JDEGOES
READ MY BLOG ON HTTP://DEGOES.NET

Post-Free: Life After Free Monads

  • 1.
  • 2.
    OUTLINE > Free What? >Live Free... > ...Or Die Hard > Exploration of Solutions > Hacks for Free > Reconstructing Free > Fixing Free > Closing Thoughts
  • 3.
    FREE WHAT? REINVENTING FREE:PURE EFFECTS data ConsoleIO = WriteLine String ConsoleIO | ReadLine (String -> ConsoleIO) | End sealed trait ConsoleIO final case class WriteLine(line: String, then: ConsoleIO) extends ConsoleIO final case class ReadLine(process: String => ConsoleIO) extends ConsoleIO final case object End extends ConsoleIO
  • 4.
    FREE WHAT? REINVENTING FREE:PURE EFFECTS W/RETURNS data ConsoleIO a = WriteLine String (ConsoleIO a) | ReadLine (String -> ConsoleIO a) | EndWith a sealed trait ConsoleIO[A] final case class WriteLine[A](line: String, then: ConsoleIO[A]) extends ConsoleIO[A] final case class ReadLine[A](process: String => ConsoleIO[A]) extends ConsoleIO[A] final case class EndWith[A](value: A) extends ConsoleIO[A]
  • 5.
    FREE WHAT? REINVENTING FREE:PURE EFFECTS W/MAP (PURESCRIPT) data ConsoleIO a = WriteLine String (ConsoleIO a) | ReadLine (String -> ConsoleIO a) | EndWith a | Map (forall z. (forall a0. ConsoleIO a0 -> (a0 -> a) -> z) -> z)
  • 6.
    FREE WHAT? REINVENTING FREE:PURE EFFECTS W/MAP (SCALA) sealed trait ConsoleIO[A] { def map[B](f: A => B): Console[B] = Map(this, f) } final case class WriteLine[A](line: String, then: ConsoleIO[A]) extends ConsoleIO[A] final case class ReadLine[A](process: String => ConsoleIO[A]) extends ConsoleIO[A] final case class EndWith[A](value: A) extends ConsoleIO[A] final case class Map[A0, A](v: ConsoleIO[A0], f: A0 => A) extends ConsoleIO[A]
  • 7.
    FREE WHAT? REINVENTING FREE:SIMPLER EFFECTS W/BIND (PURESCRIPT) data ConsoleIO a = WriteLine String a | ReadLine (String -> a) | Pure a | Chain (forall z. (forall a0. Console a0 -> (a0 -> ConsoleIO a) -> z) -> z)
  • 8.
    FREE WHAT? REINVENTING FREE:SIMPLER EFFECTS W/BIND (SCALA) sealed trait ConsoleIO[A] { def map[B](f: A => B): ConsoleIO[B] = flatMap(a => Pure[B](f(a))) def flatMap[B](f: A => ConsoleIO[B]): ConsoleIO[B] = Chain(this, f) } final case class WriteLine(line: String) extends ConsoleIO[Unit] final case class ReadLine() extends ConsoleIO[String] final case class Pure[A](value: A) extends ConsoleIO[A] final case class Chain[A0, A](v: ConsoleIO[A0], f: A0 => ConsoleIO[A]) extends ConsoleIO[A]
  • 9.
    FREE WHAT? REINVENTING FREE:SIMPLER EFFECTS W/BIND (PURESCRIPT) data ConsoleIOF a = WriteLine String a | ReadLine (String -> a) data Free f a = Pure a | Effect (f a) | Chain (forall z. ( forall a0. Free f a0 -> (a0 -> Free f a) -> z) -> z) type ConsoleIO = Free ConsoleIOF
  • 10.
    FREE WHAT? REINVENTING FREE:SIMPLER EFFECTS W/BIND (SCALA) sealed trait ConsoleIOF[A] final case class WriteLine(line: String) extends ConsoleIOF[Unit] final case class ReadLine() extends ConsoleIOF[String] sealed trait Free[F[_], A] final case class Pure[F[_], A](value: A) extends Free[F, A] final case class Effect[F[_], A](effect: F[A]) extends Free[F, A] final case class Chain[F[_], A0, A](v: Free[A0], f: A0 => Free[A]) extends Free[F, A] type ConsoleIO[A] = Free[ConsoleIOF, A]
  • 11.
    FREE WHAT? CLASSIC DEFINITIONOF FREE data Free f a = Point a | Join (f (Free f a)) sealed trait Free[F[_], A] final case class Point[F[_], A](value: A) extends Free[F, A] final case class Join[F[_], A](value: F[Free[F, A]]) extends Free[F, A]
  • 12.
    FREE WHAT? INTERPRETATION OFFREE Free f a Free[F, A] ^ ^ ^ | ------- The value produced by the program | | The effects of the program | A program that halts, runs forever, or produces an A
  • 13.
    FREE WHAT? EXAMPLE: EFFECTS dataConsoleIO a = ReadLine (String -> a) | WriteLine a String sealed trait ConsoleIO[A] final case class ReadLine[A](next: String => A) extends ConsoleIO[A] final case class WriteLine[A](next: A, line: String) extends ConsoleIO[A]
  • 14.
    FREE WHAT? EXAMPLE: HELPERS readLine:: Free ConsoleIO String readLine = liftF (ReadLine id) writeLine :: String -> Free ConsoleIO Unit writeLine = liftF <<< WriteLine unit def readLine: Free[ConsoleIO, String] = Free.liftF(ReadLine[String](identity)) def writeLine(line: String): Free[ConsoleIO, Unit] = Free.liftF(WriteLine[Unit]((), line))
  • 15.
    FREE WHAT? EXAMPLE: PROGRAM program= do writeLine "What is your name?" n <- readLine writeLine ("Hello, " <> n <> "!") return unit def program: Free[ConsoleIO, Unit] = for { _ <- writeLine("What is your name?") n <- readLine _ <- writeLine("Hello, " + n + "!") } yield ()
  • 16.
    FREE WHAT? COMPOSITION: FILEIO dataFileIO a = ReadFile (Bytes -> a) String | WriteFile a String Bytes sealed trait FileIO[A] final case class ReadFile[A](next: Bytes => A, name: String) extends FileIO[A] final case class WriteFile[A](next: A, name: String, file: Bytes) extends FileIO[A]
  • 17.
    FREE WHAT? COMPOSITION: COPRODUCT dataCoproduct f g a = Left (f a) | Right (g a) sealed trait Coproduct[F[_], G[_], A] final case class Left[F[_], G[_], A](value: F[A]) extends Coproduct[F, G, A] final case class Right[F[_], G[_], A](value: G[A]) extends Coproduct[F, G, A]
  • 18.
    FREE WHAT? COMPOSITION: PROGRAM typeProgram = Free (Coproduct FileIO ConsoleIO) type Program[A] = Free[Coproduct[FileIO, ConsoleIO, ?], A]
  • 19.
    LIVE FREE... TYPE-SAFE MOCKING mockSpec:: MockSpec ConsoleIO mockSpec = do expectWrite _WriteLine (assertEquals "What is your name?") expectRead _ReadLine "World" expectWrite _WriteLine (assertEquals "Hello, World!") def mockSpec: MockSpec[ConsoleIO] = for { _ <- expectWrite(_WriteLine, assertEquals("What is your name?")) _ <- expectRead(_ReadLine, "World") _ <- expectWrite(_WriteLine, assertEquals("Hello World")) } yield ()
  • 20.
    LIVE FREE... RUNTIME OPTIMIZATION(PURESCRIPT) data Parser a = ParseChar (Char -> a) | ParseString (String -> a) | Fail String optimize :: FreeAp Parser ~> FreeAp Parser optimize = ...
  • 21.
    LIVE FREE... RUNTIME OPTIMIZATION(SCALA) sealed trait Parser[A] final case class ParseChar[A](next: Char => A) extends Parser[A] final case class ParseString[A](next: String => A) extends Parser[A] final case class Fail[A](error: String) extends Parser[A] def optimize: FreeAp[Parser, ?] ~> FreeAp[Parser, ?] = ???
  • 22.
    LIVE FREE... ASPECT-ORIENTED PROGRAMMING(PURESCRIPT) log line = liftF (Left (WriteLine unit line)) weaveLogging :: FileIO ~> Free (Coproduct ConsoleIO FileIO) weaveLogging (ReadFile next name) = do log $ "Reading file: " <> name bytes <- liftF (Right (ReadFile id name)) log $ "File contents: " <> show bytes return (next bytes) weaveLogging (WriteFile next name bytes) = ... program :: Free FileIO Unit program = ... program' :: Free (Coproduct ConsoleIO FileIO) Unit program' = foldFree weaveLogging program
  • 23.
    LIVE FREE... ASPECT-ORIENTED PROGRAMMING(SCALA) def weaveLogging: FileIO ~> Free[Coproduct[ConsoleIO, FileIO, ?], ?] = new NaturalTransformation[FileIO, Coproduct[ConsoleIO, FileIO, ?]] { def log(line: String) = Free.liftF(Left[ConsoleIO, FileIO, Unit](WriteLine(unit, line))) def apply[A](fa: FileIO[A]): Coproduct[ConsoleIO, FileIO, A] = fa match { case (ReadFile(next, name)) => for { _ <- log("Reading file: " + name) bytes <- liftF(Right[ConsoleIO, FileIO, Bytes](ReadFile(id, name)) _ <- log("File contents: " + bytes) } yield next(bytes) case (WriteFile(next, name, bytes)) => ??? } }
  • 24.
    ...OR DIE HARD THEFREE MONAD IS ONLY A FREE MONAD > Parallelism > Failure > Alternatives > Nondeterminism > ...And all other abstractions with more structure than a monad.
  • 25.
    ...OR DIE HARD USECASE #1: CONCURRENCY loadModel = do token <- authenticate sequential $ Model <$> parallel (get "/products/popular/" token) <*> parallel (get "/categories/all" token)
  • 26.
    ...OR DIE HARD USECASE #2: NONDETERMINISM data UI a = Click (ClickEvent -> a) | KeyPress (KeyEvent -> a) | GetClass (String -> a) | SetClass String a | ... doubleClick btn = guard ((e1 e2 -> (e2.ts - e1.ts) < 200) <$> click btn <*> click btn) toggleOnDoubleClick btn = doubleClick btn *> toggleClass "toggled" btn toggleFirst = foldl (e m -> m <|> toggleOnDoubleClick e) empty buttons
  • 27.
    ...OR DIE HARD USECASE #3: FAILURE handleError (readConfig specifiedDir) (const $ readConfig defaultDir) handleError(readConfig(specifiedDir), Function.const(readConfig(defaultDir)))
  • 28.
    HACKS FOR FREE HACKINGPARALLELISM: TYPE & EFFECT type SeqPar f = Free (FreeAp f) liftFA :: forall f. f ~> SeqPar f liftFA fa = liftF (liftFreeAp fa) type SeqPar[F[_], A] = Free[FreeAp[F, ?], A] def liftFA[F[_], A](fa: F[A]): SeqPar[F, A] = Free.liftF[FreeAp[F, ?], A](FreeAp.lift(fa))
  • 29.
    HACKS FOR FREE HACKINGPARALLELISM: LIFTING PAR & SEQ liftSeq :: forall f a. Free f a -> SeqPar f a liftSeq = foldFree liftFA liftPar :: forall f a. FreeAp f a -> SeqPar f a liftPar = liftF def liftSeq[F[_], A](freefa: Free[F, A]): SeqPar[F, A] = { implicit val m: Monad[SeqPar[F, ?]] = Free.freeMonad[FreeAp[F, ?]] freefa.foldMap[SeqPar[F, ?]](new NaturalTransformation[F, SeqPar[F, ?]] { def apply[A](fa: F[A]): SeqPar[F, A] = liftFA(fa) }) } def liftPar[F[_], A](freeap: FreeAp[F, A]): SeqPar[F, A] = Free.liftF[FreeAp[F, ?], A](freeap)
  • 30.
    HACKS FOR FREE HACKINGPARALLELISM: OPTIMIZATION type ParInterpreter f g = FreeAp f ~> g type ParOptimizer f g = ParInterpreter f (SeqPar g) optimize :: forall f g a. (FreeAp f ~> SeqPar g) -> SeqPar f a -> SeqPar g a optimize = foldFree type ParInterpreter[F[_], G[_]] = FreeAp[F, ?] ~> G type ParOptimizer[F[_], G[_]] = ParInterpreter[F, SeqPar[G, ?]] def optimize[F[_], G[_], A](nt: FreeAp[F, ?] ~> SeqPar[G, ?], p: SeqPar[F, A]): SeqPar[G, A] = { implicit val m: Monad[SeqPar[G, ?]] = Free.freeMonad[FreeAp[G, ?]] p.foldMap[SeqPar[G, ?]](nt) } def parOptimize[F[_], G[_], A](nt: FreeAp[F, ?] ~> FreeAp[G, ?], p: SeqPar[F, A]): SeqPar[G, A] = optimize(new NaturalTransformation[FreeAp[F, ?], SeqPar[G, ?]] { def apply[A](freeap: FreeAp[F, A]): SeqPar[G, A] = liftPar(nt(freeap)) }, p)
  • 31.
    HACKS FOR FREE HACKINGNONDETERMINISM: TYPE data FreeAlt e f a = FreeAlt (Free (Alt e f) a) data Alt e f a = Failure e | FirstSuccess (FreeAlt e f a) (FreeAlt e f a) | Effect (f a) final case class FreeAlt[E, F[_], A](run: Free[Alt[E, F, ?], A]) sealed trait Alt[E, F[_], A] final case class Failure[E, F[_], A](error: E) extends Alt[E, F, A] final case class FirstSuccess[E, F[_], A](first: FreeAlt[E, F, A], second: FreeAlt[E, F, A], merge: (E, E) => E) extends Alt[E, F, A] final case class Effect[E, F[_], A](effect: F[A]) extends Alt[E, F, A]
  • 32.
    HACKS FOR FREE HACKINGNONDETERMINISM: INSTANCES implicit def FreeAltMonadPlus[E: Monoid, F[_]]: MonadPlus[FreeAlt[E, F, ?]] = new MonadPlus[FreeAlt[E, F, ?]] { implicit val m: Monad[Free[Alt[E, F, ?], ?]] = Free.freeMonad[Alt[E, F, ?]] def point[A](a: => A): FreeAlt[E, F, A] = FreeAlt[E, F, A](m.point(a)) def bind[A, B](fa: FreeAlt[E, F, A])(f: A => FreeAlt[E, F, B]): FreeAlt[E, F, B] = FreeAlt[E, F, B](m.bind(fa.run)(f.map(_.run))) def plus[A](a: FreeAlt[E, F, A], b: => FreeAlt[E, F, A]): FreeAlt[E, F, A] = FreeAlt(Free.liftF[Alt[E, F, ?], A]( FirstSuccess[E, F, A](a, b, Monoid[E].append(_, _)))) def empty[A]: FreeAlt[E, F, A] = FreeAlt(Free.liftF[Alt[E, F, ?], A](Failure[E, F, A](Monoid[E].zero))) }
  • 33.
    HACKS FOR FREE HACKINGNONDETERMINISM: IMPROVING COMPOSABILITY (PURESCRIPT) data FreeAlt e f a = Free (Alt FreeAlt e f) a data Alt t e f a = Failure e | FirstSuccess (t e f a) (t e f a) | Effect (f a)
  • 34.
    HACKS FOR FREE HACKINGNONDETERMINISM: IMPROVING COMPOSABILITY (SCALA) final case class FreeAlt[E, F[_], A](run: Free[Alt[FreeAlt, E, F, ?], A]) sealed trait Alt[T[_, _[_], _], E, F[_], A] final case class Failure[E, F[_], A](error: E) extends Alt[E, F, A] final case class FirstSuccess[E, F[_], A](first: T[E, F, A], second: T[E, F, A], merge: (E, E) => E) extends Alt[E, F, A] final case class Effect[E, F[_], A](effect: F[A]) extends Alt[E, F, A]
  • 35.
    HACKS FOR FREE THEPROBLEMS WITH HACKING > Composes poorly, and at great cost to performance, usability data MyFree f a = MyFree (Free ( Alt MyFree ( Parallel MyFree ( Race MyFree f))) a) final case class MyFree[F[_], A]( run: Free[Alt[MyFree, Parallel[MyFree, Race[MyFree, F, ?], ?], ?], A]) > Blurs machinery & effects > Where is effect? It's nested inside n-levels of machinery.
  • 36.
    HACKS FOR FREE WISHLIST > Improve performance > Improve usability > Cleanly separate effects and machinery
  • 37.
    RECONSTRUCTING FREE TYPE DEFINITION(PURESCRIPT) data FreeStar e f a ^ ^ ^ | | | Error| Return Value | Effect
  • 38.
    RECONSTRUCTING FREE TYPE DEFINITION(SCALA) sealed trait FreeStar[E, F[_], A] ^ ^ ^ | | | Error | Return Value | Effect
  • 39.
    RECONSTRUCTING FREE TYPE DEFINITION(PURESCRIPT) data FreeStar e f a = Pure a | Effect (f a) | Sequence (forall z. (forall a0. FreeStar e f a0 -> (a0 -> FreeStar e f a) -> z) -> z) | Parallel (forall z. (forall l r. FreeStar e f l -> FreeStar e f r -> (l -> r -> a) -> z) -> z) | Failure e | Recover (FreeStar e f a) (e -> FreeStar e f a) | FirstSuccess (FreeStar e f a) (FreeStar e f a)
  • 40.
    RECONSTRUCTING FREE PURITY final caseclass Pure[E, F[_], A](a: A) extends FreeStar[E, F, A]
  • 41.
    RECONSTRUCTING FREE EFFECTS final caseclass Effect[E, F[_], A](fa: F[A]) extends FreeStar[E, F, A]
  • 42.
    RECONSTRUCTING FREE SEQUENCING sealed traitSequence[E, F[_], A] extends FreeStar[E, F, A] { type A0 def a: FreeStar[E, F, A0] def f: A0 => FreeStar[E, F, A] }
  • 43.
    RECONSTRUCTING FREE PARALLELISM sealed traitParallel[E, F[_], A] extends FreeStar[E, F, A] { type B type C def left: FreeStar[E, F, B] def right: FreeStar[E, F, C] def join: (B, C) => A }
  • 44.
    RECONSTRUCTING FREE FAILURE final caseclass Failure[E, F[_], A](error: E) extends FreeStar[E, F, A] final case class Recover[E, F[_], A]( value: FreeStar[E, F, A], f: E => FreeStar[E, F, A]) extends FreeStar[E, F, A]
  • 45.
    RECONSTRUCTING FREE NONDETERMINISM final caseclass FirstSuccess[E, F[_], A]( first: FreeStar[E, F, A], second: FreeStar[E, F, A]) extends FreeStar[E, F, A]
  • 46.
    RECONSTRUCTING FREE INSTANCES implicit defFreeStarMonadPlus[E: Monoid, F[_]] = new MonadPlus[FreeStar[E, F, ?]] with MonadError[FreeStar[E, F, ?], E] { def point[A](a: => A): FreeStar[E, F, A] = Pure[E, F, A](a) def bind[A, B](fa: FreeStar[E, F, A])(f: A => FreeStar[E, F, B]): FreeStar[E, F, B] = Sequence[A, E, F, B](fa, f) def plus[A](a: FreeStar[E, F, A], b: => FreeStar[E, F, A]): FreeStar[E, F, A] = FirstSuccess(a, b) def empty[A]: FreeStar[E, F, A] = Failure[E, F, A](Monoid[E].zero) def raiseError[A](e: E): FreeStar[E, F, A] = Failure(e) def handleError[A](fa: FreeStar[E, F, A])(f: E => FreeStar[E, F, A]): FreeStar[E, F, A] = Recover(fa, f) }
  • 47.
    RECONSTRUCTING FREE THE PROBLEMSWITH RECONSTRUCTION > Whole program has access to all features > Constraints (on features) liberate (interpreters) > Liberties (on features) constrain (interpreters) > Cannot express one feature in terms of others > Must interpret all features at once
  • 48.
    RECONSTRUCTING FREE WISH LIST >Fine-grained features — pay for what you use > Compositional features > Compositional interpreters
  • 49.
    FIXING FREE DETOUR: RECURSIVEEXPR data Expr = Lit Int | Add Expr Expr sealed trait Expr final case class Lit(value: Int) extends Expr final case class Add(left: Expr, right: Expr) extends Expr
  • 50.
    FIXING FREE DETOUR: FIXEDEXPR data Expr a = Lit Int | Add a a data Fixed f = Fixed (f (Fixed f)) type RecursiveExpr = Fixed Expr sealed trait Expr[A] final case class Lit[A](value: Int) extends Expr[A] final case class Add[A](left: A, right: A) extends Expr[A] final case class Fixed[F[_]](unfix: F[Fixed[F]]) type RecursiveExpr = Fixed[Expr]
  • 51.
    FIXING FREE DETOUR: RECURSIVELIST data List a = Empty | Cons a (List a) sealed trait List[A] final case class Empty[A]() extends List[A] final case class Cons[A](head: A, tail: List[A]) extends List[A]
  • 52.
    FIXING FREE DETOUR: FIXEDLIST data ListF z a = Empty | Cons a (z a) data Fixed t a = Fixed (t (Fixed t) a) type List = Fixed LiftF sealed trait ListF[Z[_], A] final case class Empty[Z[_], A]() extends ListF[Z, A] final case class Cons[Z[_], A](head: A, tail: Z[A]) extends ListF[Z, A] final case class Fixed[T[_[_], _], A](unfix: T[Fixed[T, ?], A]) type List[A] = Fixed[ListF, A]
  • 53.
    FIXING FREE DETOUR: FIXEDLIST — EMPTY, CONS, UNCONS empty :: List a empty = Fixed Empty cons :: a -> List a -> List a cons a as = Fixed (Cons a as) uncons :: List a -> Maybe (Tuple a List a) uncons (Fixed Empty) = Nothing uncons (Fixed (Cons a as)) = Just (Tuple a as) def empty[A]: List[A] = Fixed[ListF, A](Empty[List, A](): ListF[List, A]) def cons[A](a: A, as: List[A]): List[A] = Fixed[ListF, A](Cons[List, A](a, as): ListF[List, A]) def uncons[A](as: List[A]): Option[(A, List[A])] = as match { case Fixed(l) => (l : ListF[List, A]) match { case x : Empty[List, A] => None case x : Cons[List, A] => Some((x.head, x.tail)) } }
  • 54.
    FIXING FREE DETOUR: FIXEDLIST — COMPOSABLE TERMS (PURESCRIPT) data Empty z a = Empty data Cons z a = Cons a (z a) data Concat z a = Concat (z a) (z a) data Coproduct t1 t2 z a = CLeft (t1 z a) | CRight (t2 z a) data Fixed t a = Fixed (t (Fixed t) a) type ConsOrCat = Coproduct Cons Concat type EmptyOrConsOrCat = Coproduct Empty ConsOrCat type List a = Fixed EmptyOrConsOrCat a
  • 55.
    FIXING FREE DETOUR: FIXEDLIST — COMPOSABLE TERMS (SCALA) final case class Empty[Z[_], A]() final case class Cons[Z[_], A](head: A, tail: Z[A]) final case class Concat[Z[_], A](first: Z[A], last: Z[A]) sealed trait Coproduct[T1[_[_], _], T2[_[_], _], Z[_], A] case class CLeft[T1[_[_], _], T2[_[_], _], Z[_], A](value: T1[Z, A]) extends Coproduct[T1, T2, Z, A] case class CRight[T1[_[_], _], T2[_[_], _], Z[_], A](value: T2[Z, A]) extends Coproduct[T1, T2, Z, A] final case class Fixed[T[_[_], _], A](unfix: T[Fixed[T, ?], A]) type ConsOrCat[Z[_], A] = Coproduct[Cons, Concat, Z, A] type EmptyOrConsOrCat[Z[_], A] = Coproduct[Empty, ConsOrCat, Z, A] type List[A] = Fixed[EmptyOrConsOrCat, A]
  • 56.
    FIXING FREE DETOUR: FIXEDLIST — COMPOSABLE TERMS (PURESCRIPT) empty :: forall a. List a empty = Fixed (CLeft Empty) cons :: forall a. a -> List a -> List a cons a as = Fixed <<< CRight <<< CLeft $ Cons a as concat :: forall a. List a -> List a -> List a concat a1 a2 = Fixed <<< CRight <<< CRight $ Concat a1 a2 uncons :: forall a. List a -> Maybe (Tuple a (List a)) uncons (Fixed f) = case f of CLeft Empty -> Nothing CRight (CLeft (Cons a as)) -> Just (Tuple a as) CRight (CRight (Concat a1 a2)) -> case uncons a1 of Nothing -> Nothing Just (Tuple a as) -> Just (Tuple a (concat as a2))
  • 57.
    FIXING FREE DETOUR: FIXEDLIST — COMPOSABLE TERMS (SCALA) def empty[A]: List[A] = Fixed[EmptyOrConsOrCat, A]( CLeft[Empty, ConsOrCat, List, A](Empty[List, A]())) def cons[A](a: A, as: List[A]): List[A] = Fixed[EmptyOrConsOrCat, A]( CRight[Empty, ConsOrCat, List, A]( CLeft[Cons, Concat, List, A](Cons[List, A](a, as)))) def concat[A](first: List[A], last: List[A]): List[A] = Fixed[EmptyOrConsOrCat, A]( CRight[Empty, ConsOrCat, List, A]( CRight[Cons, Concat, List, A](Concat[List, A](first, last)))) def uncons[A](as: List[A]): Option[(A, List[A])] = as.unfix match { case _ : CLeft[Empty, ConsOrCat, List, A] => None case v : CRight[Empty, ConsOrCat, List, A] => v.value match { case v : CLeft[Cons, Concat, List, A] => Some((v.value.head, v.value.tail)) case v : CRight[Cons, Concat, List, A] => uncons(v.value.first) match { case None => uncons(v.value.last) case Some((a, as)) => Some((a, concat(as, v.value.last))) } } }
  • 58.
    FIXING FREE BASIC TERMDEFINITION A program that halts, runs forever, or produces an A ^ | | t z e a T[Z[_], E[_], A] ^ ^ ^ | | | | | | Self | | Effect | Return Kind: (* -> *) -> (* -> *) -> * -> *
  • 59.
    FIXING FREE EXTENDED TERMDEFINITION A program that halts, errors with an E, runs forever, or produces an A ^ | | t e z e a T[E, Z[_], E[_], A] ^ ^ ^ ^ | | | | | | | | | Self | | | Effect | | Return Error Kind: * -> (* -> *) -> (* -> *) -> * -> *
  • 60.
    FIXING FREE PURITY data Puree z f a = Pure a final case class Pure[E, Z[_], F[_], A](a: A)
  • 61.
    FIXING FREE EFFECTS data Effecte z f a = Effect (f a) final case class Effect[E, Z[_], F[_], A](fa: F[A])
  • 62.
    FIXING FREE SEQUENCE data Sequencee z f a = Sequence (forall x. (forall a0. z a0 -> (a0 -> z a) -> x) -> x) trait Sequence[E, Z[_], F[_], A] { type A0 def a: Z[A0] def f: A0 => Z[A] }
  • 63.
    FIXING FREE PARALLEL data Parallele z f a = Parallel ( forall w. ( forall l r. z l -> z r -> (l -> r -> a) -> w) -> w) trait Parallel[E, Z[_], F[_], A] { type B type C def left: Z[B] def right: Z[C] def join: (B, C) => A }
  • 64.
    FIXING FREE FAILURE data Failuree z f a = Failure e data Recover e z f a = Recover (z a) (e -> z a) case class Failure[E, Z[_], F[_], A](error: E) case class Recover[E, Z[_], F[_], A](value: Z[A], f: E => Z[A])
  • 65.
    FIXING FREE ORDERED ALTERNATE dataFirstSuccess e z f a = FirstSuccess (z a) (z a) case class FirstSuccess[E, Z[_], F[_], A](first: Z[A], second: Z[A])
  • 66.
    FIXING FREE TERM COMPOSITION dataEitherF t1 t2 e z f a = LeftF (t1 e z f a) | RightF (t1 e z f a) sealed trait EitherF[T1[_, _[_], _[_], _], T2[_, _[_], _[_], _], E, Z[_], F[_], A] final case class LeftF[T1[_, _[_], _[_], _], T2[_, _[_], _[_], _], E, Z[_], F[_], A]( value: T1[E, Z, F, A]) extends EitherF[T1, T2, E, Z, F, A] final case class RightF[T1[_, _[_], _[_], _], T2[_, _[_], _[_], _], E, Z[_], F[_], A]( value: T2[E, Z, F, A]) extends EitherF[T1, T2, E, Z, F, A]
  • 67.
    FIXING FREE FIXING data Fixedt e f a = Fixed (t e (Fixed t e f) f a) final case class Fixed[T[_, _[_], _[_], _], E, F[_], A]( unfix: T[E, Fixed[T, E, F, ?], F, A] )
  • 68.
    FIXING FREE THE PROBLEMSWITH FIXING > Straining language capabilities > "Benign" boilerplate > But...type-level or macro machinery to the rescue?
  • 69.
    FIXING FREE WISH LIST >Language-level support for positively & negatively constrained, heterogeneous sets (unions) > Recursion as a computational feature > i.e. A NEW PROGRAMMING LANGUAGE
  • 70.
    CLOSING THOUGHTS WHERE WEARE TODAY > "Post-free" is about reifying the structure of computation > By constraining the structure of subprograms, we liberate interpretation of them > We can do "post-free" now using a variety of techniques
  • 71.
    CLOSING THOUGHTS WHERE WECOULD GO TOMORROW > Post-free points to a world where programs are defined by: > (a) Defining computational features in terms of others > (b) Defining program effects in terms of others (onion architecture) > (c) Type-safe weaving, introspection, mocking, optimization, etc. > (d) Purely denotational semantics for programs
  • 72.
    THANK YOUFOLLOW MEON TWITTER AT @JDEGOES READ MY BLOG ON HTTP://DEGOES.NET