72
POST-FREE: LIFE AFTER FREE MONADS JOHN A. DE GOES — @JDEGOES

Post-Free: Life After Free Monads

Embed Size (px)

Citation preview

Page 1: Post-Free: Life After Free Monads

POST-FREE: LIFE AFTER

FREE MONADSJOHN A. DE GOES — @JDEGOES

Page 2: Post-Free: Life After Free Monads

OUTLINE> Free What?> Live Free...

> ...Or Die Hard> Exploration of Solutions

> Hacks for Free> Reconstructing Free

> Fixing Free> Closing Thoughts

Page 3: Post-Free: Life After Free Monads

FREE WHAT?REINVENTING FREE: PURE EFFECTS

data ConsoleIO = WriteLine String ConsoleIO | ReadLine (String -> ConsoleIO) | End

sealed trait ConsoleIOfinal case class WriteLine(line: String, then: ConsoleIO) extends ConsoleIOfinal case class ReadLine(process: String => ConsoleIO) extends ConsoleIOfinal case object End extends ConsoleIO

Page 4: Post-Free: Life After Free Monads

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]

Page 5: Post-Free: Life After Free Monads

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)

Page 6: Post-Free: Life After Free Monads

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]

Page 7: Post-Free: Life After Free Monads

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)

Page 8: Post-Free: Life After Free Monads

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]

Page 9: Post-Free: Life After Free Monads

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

Page 10: Post-Free: Life After Free Monads

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]

Page 11: Post-Free: Life After Free Monads

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]

Page 12: Post-Free: Life After Free Monads

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

Page 13: Post-Free: Life After Free Monads

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]

Page 14: Post-Free: Life After Free Monads

FREE WHAT?EXAMPLE: HELPERS

readLine :: Free ConsoleIO StringreadLine = liftF (ReadLine id)

writeLine :: String -> Free ConsoleIO UnitwriteLine = 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))

Page 15: Post-Free: Life After Free Monads

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 ()

Page 16: Post-Free: Life After Free Monads

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]

Page 17: Post-Free: Life After Free Monads

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]

Page 18: Post-Free: Life After Free Monads

FREE WHAT?COMPOSITION: PROGRAM

type Program = Free (Coproduct FileIO ConsoleIO)

type Program[A] = Free[Coproduct[FileIO, ConsoleIO, ?], A]

Page 19: Post-Free: Life After Free Monads

LIVE FREE...TYPE-SAFE MOCKING

mockSpec :: MockSpec ConsoleIOmockSpec = 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 ()

Page 20: Post-Free: Life After Free Monads

LIVE FREE...RUNTIME OPTIMIZATION (PURESCRIPT)

data Parser a = ParseChar (Char -> a) | ParseString (String -> a) | Fail String

optimize :: FreeAp Parser ~> FreeAp Parseroptimize = ...

Page 21: Post-Free: Life After Free Monads

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, ?] = ???

Page 22: Post-Free: Life After Free Monads

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 Unitprogram = ...

program' :: Free (Coproduct ConsoleIO FileIO) Unitprogram' = foldFree weaveLogging program

Page 23: Post-Free: Life After Free Monads

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)) => ??? } }

Page 24: Post-Free: Life After Free Monads

...OR DIE HARDTHE FREE MONAD IS ONLY A FREE MONAD

> Parallelism> Failure

> Alternatives> Nondeterminism

> ...And all other abstractions with more structure than a monad.

Page 25: Post-Free: Life After Free Monads

...OR DIE HARDUSE CASE #1: CONCURRENCY

loadModel = do token <- authenticate sequential $ Model <$> parallel (get "/products/popular/" token) <*> parallel (get "/categories/all" token)

Page 26: Post-Free: Life After Free Monads

...OR DIE HARDUSE 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

Page 27: Post-Free: Life After Free Monads

...OR DIE HARDUSE CASE #3: FAILURE

handleError (readConfig specifiedDir) (const $ readConfig defaultDir)

handleError(readConfig(specifiedDir), Function.const(readConfig(defaultDir)))

Page 28: Post-Free: Life After Free Monads

HACKS FOR FREEHACKING PARALLELISM: TYPE & EFFECT

type SeqPar f = Free (FreeAp f)

liftFA :: forall f. f ~> SeqPar fliftFA 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))

Page 29: Post-Free: Life After Free Monads

HACKS FOR FREEHACKING PARALLELISM: LIFTING PAR & SEQ

liftSeq :: forall f a. Free f a -> SeqPar f aliftSeq = foldFree liftFA

liftPar :: forall f a. FreeAp f a -> SeqPar f aliftPar = 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)

Page 30: Post-Free: Life After Free Monads

HACKS FOR FREEHACKING PARALLELISM: OPTIMIZATION

type ParInterpreter f g = FreeAp f ~> gtype ParOptimizer f g = ParInterpreter f (SeqPar g)

optimize :: forall f g a. (FreeAp f ~> SeqPar g) -> SeqPar f a -> SeqPar g aoptimize = foldFree

type ParInterpreter[F[_], G[_]] = FreeAp[F, ?] ~> Gtype 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)

Page 31: Post-Free: Life After Free Monads

HACKS FOR FREEHACKING 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]

Page 32: Post-Free: Life After Free Monads

HACKS FOR FREEHACKING 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)))}

Page 33: Post-Free: Life After Free Monads

HACKS FOR FREEHACKING 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)

Page 34: Post-Free: Life After Free Monads

HACKS FOR FREEHACKING 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]

Page 35: Post-Free: Life After Free Monads

HACKS FOR FREETHE PROBLEMS WITH HACKING

> Composes poorly, and at great cost to performance, usabilitydata 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.

Page 36: Post-Free: Life After Free Monads

HACKS FOR FREEWISH LIST

> Improve performance> Improve usability

> Cleanly separate effects and machinery

Page 37: Post-Free: Life After Free Monads

RECONSTRUCTING FREETYPE DEFINITION (PURESCRIPT)

data FreeStar e f a ^ ^ ^ | | | Error| Return Value | Effect

Page 38: Post-Free: Life After Free Monads

RECONSTRUCTING FREETYPE DEFINITION (SCALA)

sealed trait FreeStar[E, F[_], A] ^ ^ ^ | | | Error | Return Value | Effect

Page 39: Post-Free: Life After Free Monads

RECONSTRUCTING FREETYPE 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)

Page 40: Post-Free: Life After Free Monads

RECONSTRUCTING FREEPURITY

final case class Pure[E, F[_], A](a: A) extends FreeStar[E, F, A]

Page 41: Post-Free: Life After Free Monads

RECONSTRUCTING FREEEFFECTS

final case class Effect[E, F[_], A](fa: F[A]) extends FreeStar[E, F, A]

Page 42: Post-Free: Life After Free Monads

RECONSTRUCTING FREESEQUENCING

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]}

Page 43: Post-Free: Life After Free Monads

RECONSTRUCTING FREEPARALLELISM

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}

Page 44: Post-Free: Life After Free Monads

RECONSTRUCTING FREEFAILURE

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]

Page 45: Post-Free: Life After Free Monads

RECONSTRUCTING FREENONDETERMINISM

final case class FirstSuccess[E, F[_], A]( first: FreeStar[E, F, A], second: FreeStar[E, F, A]) extends FreeStar[E, F, A]

Page 46: Post-Free: Life After Free Monads

RECONSTRUCTING FREEINSTANCES

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) }

Page 47: Post-Free: Life After Free Monads

RECONSTRUCTING FREETHE 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

Page 48: Post-Free: Life After Free Monads

RECONSTRUCTING FREEWISH LIST

> Fine-grained features — pay for what you use> Compositional features

> Compositional interpreters

Page 49: Post-Free: Life After Free Monads

FIXING FREEDETOUR: RECURSIVE EXPR

data Expr = Lit Int | Add Expr Expr

sealed trait Exprfinal case class Lit(value: Int) extends Exprfinal case class Add(left: Expr, right: Expr) extends Expr

Page 50: Post-Free: Life After Free Monads

FIXING FREEDETOUR: 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]

Page 51: Post-Free: Life After Free Monads

FIXING FREEDETOUR: 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]

Page 52: Post-Free: Life After Free Monads

FIXING FREEDETOUR: 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]

Page 53: Post-Free: Life After Free Monads

FIXING FREEDETOUR: FIXED LIST — EMPTY, CONS, UNCONS

empty :: List aempty = Fixed Empty

cons :: a -> List a -> List acons a as = Fixed (Cons a as)

uncons :: List a -> Maybe (Tuple a List a)uncons (Fixed Empty) = Nothinguncons (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)) }}

Page 54: Post-Free: Life After Free Monads

FIXING FREEDETOUR: FIXED LIST — COMPOSABLE TERMS (PURESCRIPT)

data Empty z a = Emptydata 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

Page 55: Post-Free: Life After Free Monads

FIXING FREEDETOUR: 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]

Page 56: Post-Free: Life After Free Monads

FIXING FREEDETOUR: FIXED LIST — COMPOSABLE TERMS (PURESCRIPT)

empty :: forall a. List aempty = Fixed (CLeft Empty)

cons :: forall a. a -> List a -> List acons a as = Fixed <<< CRight <<< CLeft $ Cons a as

concat :: forall a. List a -> List a -> List aconcat 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))

Page 57: Post-Free: Life After Free Monads

FIXING FREEDETOUR: 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))) } }}

Page 58: Post-Free: Life After Free Monads

FIXING FREEBASIC TERM DEFINITION

A program that halts, runs forever, or produces an A ^ | | t z e a T[Z[_], E[_], A] ^ ^ ^ | | | | | | Self | | Effect | Return

Kind: (* -> *) -> (* -> *) -> * -> *

Page 59: Post-Free: Life After Free Monads

FIXING FREEEXTENDED TERM DEFINITION

A program that halts, errorswith an E, runs forever, or produces an A ^ | | t e z e a T[E, Z[_], E[_], A] ^ ^ ^ ^ | | | | | | | | | Self | | | Effect | | Return Error

Kind: * -> (* -> *) -> (* -> *) -> * -> *

Page 60: Post-Free: Life After Free Monads

FIXING FREEPURITY

data Pure e z f a = Pure a

final case class Pure[E, Z[_], F[_], A](a: A)

Page 61: Post-Free: Life After Free Monads

FIXING FREEEFFECTS

data Effect e z f a = Effect (f a)

final case class Effect[E, Z[_], F[_], A](fa: F[A])

Page 62: Post-Free: Life After Free Monads

FIXING FREESEQUENCE

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]}

Page 63: Post-Free: Life After Free Monads

FIXING FREEPARALLEL

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}

Page 64: Post-Free: Life After Free Monads

FIXING FREEFAILURE

data Failure e z f a = Failure edata 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])

Page 65: Post-Free: Life After Free Monads

FIXING FREEORDERED 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])

Page 66: Post-Free: Life After Free Monads

FIXING FREETERM 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]

Page 67: Post-Free: Life After Free Monads

FIXING FREEFIXING

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])

Page 68: Post-Free: Life After Free Monads

FIXING FREETHE PROBLEMS WITH FIXING

> Straining language capabilities> "Benign" boilerplate

> But...type-level or macro machinery to the rescue?

Page 69: Post-Free: Life After Free Monads

FIXING FREEWISH LIST

> Language-level support for positively & negatively constrained, heterogeneous sets (unions)> Recursion as a computational feature

> i.e. A NEW PROGRAMMING LANGUAGE

Page 70: Post-Free: Life After Free Monads

CLOSING THOUGHTSWHERE 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

Page 71: Post-Free: Life After Free Monads

CLOSING THOUGHTSWHERE 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

Page 72: Post-Free: Life After Free Monads

THANK YOUFOLLOW ME ON TWITTER AT @JDEGOESREAD MY BLOG ON HTTP://DEGOES.NET