Stop Paying for Free Monads Mark Hopkins @antiselfdual - - PowerPoint PPT Presentation

stop paying for free monads
SMART_READER_LITE
LIVE PREVIEW

Stop Paying for Free Monads Mark Hopkins @antiselfdual - - PowerPoint PPT Presentation

Stop Paying for Free Monads Mark Hopkins @antiselfdual Commonwealth Bank YOW LambdaJam 2016 prog tbl = do rows <- loadDb tbl dir <- mkTempDir report <- performAnalysis rows dir delete dir upload report s3Credentials $


slide-1
SLIDE 1

Stop Paying for Free Monads

Mark Hopkins @antiselfdual Commonwealth Bank YOW LambdaJam 2016

slide-2
SLIDE 2

prog tbl = do rows <- loadDb tbl dir <- mkTempDir report <- performAnalysis rows dir delete dir upload report s3Credentials $ "/reports/" ++ show tbl log $ "Wrote TPS report for " + show tbl

slide-3
SLIDE 3

Abstraction and modularity for effectful programs.

slide-4
SLIDE 4

Goals

slide-5
SLIDE 5

#1 Abstraction

slide-6
SLIDE 6

Interface/implementation separation

Rather than directly implementing our programs, I want to write a specification that I later implement or interpret.

slide-7
SLIDE 7

Interface/implementation separation

Rather than directly implementing our programs, I want to write a specification that I later implement or interpret. This allows me to

◮ swap out implementations

slide-8
SLIDE 8

Interface/implementation separation

Rather than directly implementing our programs, I want to write a specification that I later implement or interpret. This allows me to

◮ swap out implementations ◮ test using mock implementations

slide-9
SLIDE 9

Interface/implementation separation

Rather than directly implementing our programs, I want to write a specification that I later implement or interpret. This allows me to

◮ swap out implementations ◮ test using mock implementations ◮ use notions of interpretation, e.g.

slide-10
SLIDE 10

Interface/implementation separation

Rather than directly implementing our programs, I want to write a specification that I later implement or interpret. This allows me to

◮ swap out implementations ◮ test using mock implementations ◮ use notions of interpretation, e.g.

◮ evaluation

slide-11
SLIDE 11

Interface/implementation separation

Rather than directly implementing our programs, I want to write a specification that I later implement or interpret. This allows me to

◮ swap out implementations ◮ test using mock implementations ◮ use notions of interpretation, e.g.

◮ evaluation ◮ pretty-printing

slide-12
SLIDE 12

Interface/implementation separation

Rather than directly implementing our programs, I want to write a specification that I later implement or interpret. This allows me to

◮ swap out implementations ◮ test using mock implementations ◮ use notions of interpretation, e.g.

◮ evaluation ◮ pretty-printing ◮ serialization

slide-13
SLIDE 13

Some different ways of viewing this:

◮ interface vs implementation

slide-14
SLIDE 14

Some different ways of viewing this:

◮ interface vs implementation ◮ syntax vs semantics

slide-15
SLIDE 15

Some different ways of viewing this:

◮ interface vs implementation ◮ syntax vs semantics ◮ a DSL with multiple interpreters

slide-16
SLIDE 16

Some different ways of viewing this:

◮ interface vs implementation ◮ syntax vs semantics ◮ a DSL with multiple interpreters ◮ compiling a source language to a target language

slide-17
SLIDE 17

Additionally, we want low-cost abstraction.

slide-18
SLIDE 18

Additionally, we want low-cost abstraction. We don’t want to be penalised with

◮ poorly performing code

slide-19
SLIDE 19

Additionally, we want low-cost abstraction. We don’t want to be penalised with

◮ poorly performing code ◮ code that’s overly difficult to read or write.

slide-20
SLIDE 20

#2 Modularity

slide-21
SLIDE 21

I want modular languages.

slide-22
SLIDE 22

I want modular languages. I want the ability to

◮ refactor one big DSL into smaller DSLs

slide-23
SLIDE 23

I want modular languages. I want the ability to

◮ refactor one big DSL into smaller DSLs ◮ use multiple DSLs together in the same program

slide-24
SLIDE 24

I want modular languages. I want the ability to

◮ refactor one big DSL into smaller DSLs ◮ use multiple DSLs together in the same program ◮ extend a language by adding new operations

slide-25
SLIDE 25

I want modular languages. I want the ability to

◮ refactor one big DSL into smaller DSLs ◮ use multiple DSLs together in the same program ◮ extend a language by adding new operations ◮ implement a language in terms of a smaller core language.

slide-26
SLIDE 26

I want modular languages. I want the ability to

◮ refactor one big DSL into smaller DSLs ◮ use multiple DSLs together in the same program ◮ extend a language by adding new operations ◮ implement a language in terms of a smaller core language.

slide-27
SLIDE 27

I want modular languages. I want the ability to

◮ refactor one big DSL into smaller DSLs ◮ use multiple DSLs together in the same program ◮ extend a language by adding new operations ◮ implement a language in terms of a smaller core language.

So we need some kind of “addition of DSLs”.

slide-28
SLIDE 28

I want modular languages. I want the ability to

◮ refactor one big DSL into smaller DSLs ◮ use multiple DSLs together in the same program ◮ extend a language by adding new operations ◮ implement a language in terms of a smaller core language.

So we need some kind of “addition of DSLs”. There should be a kind of stability too: If I write a program, and then extend the language, my program should still be valid in the new language.

slide-29
SLIDE 29

In addition, I also want modular interpreters.

slide-30
SLIDE 30

In addition, I also want modular interpreters. If I write interpreters for a number of different languages. . .

slide-31
SLIDE 31

In addition, I also want modular interpreters. If I write interpreters for a number of different languages. . . . . . I want to reuse them to interpret a program written using them together.

slide-32
SLIDE 32

In other words, we need a solution of the expression problem.

slide-33
SLIDE 33

#3 Sequencing, binding and effects

slide-34
SLIDE 34

The (pure) functional programming vision

Better software through programs we can reason about.

slide-35
SLIDE 35

The (pure) functional programming vision

Better software through programs we can reason about. No matter how complex our programs their behaviour remains predictable.

slide-36
SLIDE 36

The lambda calculus

Functions are values: their meaning does not depend on their context.

slide-37
SLIDE 37

The lambda calculus

Functions are values: their meaning does not depend on their context. The laws that function composition satisfies f . id = f = id . f f . (g . h) = (f . g) . h allow us to reason about our code and to safety refactor. i.e. equational reasoning.

slide-38
SLIDE 38

How can we add side effects but retain predictability?

slide-39
SLIDE 39

Moggi’s insight was to use the type system.1

1Eugenio Moggi, Notions of computation as monads, 1991

slide-40
SLIDE 40

Moggi’s insight was to use the type system.1 Types will stand in for effects.

1Eugenio Moggi, Notions of computation as monads, 1991

slide-41
SLIDE 41

Looking at our function type f : a -> b

slide-42
SLIDE 42

Looking at our function type f : a -> b there are three things we can modify:

slide-43
SLIDE 43

Looking at our function type f : a -> b there are three things we can modify:

◮ source

a

slide-44
SLIDE 44

Looking at our function type f : a -> b there are three things we can modify:

◮ source

a

◮ target

b

slide-45
SLIDE 45

Looking at our function type f : a -> b there are three things we can modify:

◮ source

a

◮ target

b

◮ arrow

  • >
slide-46
SLIDE 46

Looking at our function type f : a -> b there are three things we can modify:

◮ source

a

◮ target

b

◮ arrow

  • >
slide-47
SLIDE 47

Looking at our function type f : a -> b there are three things we can modify:

◮ source

a

◮ target

b

◮ arrow

  • >

This gives us three basic type classes for structuring computations

◮ comonads

w a -> b

slide-48
SLIDE 48

Looking at our function type f : a -> b there are three things we can modify:

◮ source

a

◮ target

b

◮ arrow

  • >

This gives us three basic type classes for structuring computations

◮ comonads

w a -> b

◮ monads

a -> m b

slide-49
SLIDE 49

Looking at our function type f : a -> b there are three things we can modify:

◮ source

a

◮ target

b

◮ arrow

  • >

This gives us three basic type classes for structuring computations

◮ comonads

w a -> b

◮ monads

a -> m b

◮ categories (and arrows)

a >>> b

slide-50
SLIDE 50

Equational reasoning

Reinstating the rules that we had for functions gives us the laws these have to obey: f =>= extract = f = extract =>= f f >=> return = f = return >=> f f >>> id = f = id >>> f (f =>= g) =>= h = f =>= (g =>= h) (f >=> g) >=> h = f >=> (g >=> h) (f >>> g) >>> h = f >>> (g >>> h)

slide-51
SLIDE 51

Equational reasoning

Reinstating the rules that we had for functions gives us the laws these have to obey: f =>= extract = f = extract =>= f f >=> return = f = return >=> f f >>> id = f = id >>> f (f =>= g) =>= h = f =>= (g =>= h) (f >=> g) >=> h = f >=> (g >=> h) (f >>> g) >>> h = f >>> (g >>> h) We can use these to refactor safely, i.e. we have equational reasoning in the presence of effects.

slide-52
SLIDE 52

This generalises beyond side effects. We use effect (or context) as a way of talking about the enhancement that m a brings over the raw type a.

slide-53
SLIDE 53

We want to reuse these patterns that FP has evolved to deal with effects, sequencing and binding.

slide-54
SLIDE 54

#4 Effects in the specification

Most literature talks about a pure specification with effectful interpreters.

slide-55
SLIDE 55

#4 Effects in the specification

Most literature talks about a pure specification with effectful interpreters. But sometimes, creating or handling effects belongs in the specification.

slide-56
SLIDE 56

#4 Effects in the specification

Most literature talks about a pure specification with effectful interpreters. But sometimes, creating or handling effects belongs in the specification. e.g. we want to

◮ fail early if some condition is not met.

slide-57
SLIDE 57

#4 Effects in the specification

Most literature talks about a pure specification with effectful interpreters. But sometimes, creating or handling effects belongs in the specification. e.g. we want to

◮ fail early if some condition is not met. ◮ clean up after some operation

slide-58
SLIDE 58

#4 Effects in the specification

Most literature talks about a pure specification with effectful interpreters. But sometimes, creating or handling effects belongs in the specification. e.g. we want to

◮ fail early if some condition is not met. ◮ clean up after some operation

slide-59
SLIDE 59

#4 Effects in the specification

Most literature talks about a pure specification with effectful interpreters. But sometimes, creating or handling effects belongs in the specification. e.g. we want to

◮ fail early if some condition is not met. ◮ clean up after some operation

And this needs to obey appropriate laws.

slide-60
SLIDE 60

Solution 1: Reify

slide-61
SLIDE 61

Datatypes à la carte, Wouter Swierstra, 2008 Motto: Turn operations into constructors

slide-62
SLIDE 62

Let’s start with a simple typed language of console interactions.

slide-63
SLIDE 63

Let’s start with a simple typed language of console interactions. Following our motto, let’s express it as a data type. data Console a where Ask :: String -> Console String Tell :: String -> Console ()

slide-64
SLIDE 64

Let’s start with a simple typed language of console interactions. Following our motto, let’s express it as a data type. data Console a where Ask :: String -> Console String Tell :: String -> Console () Each operation becomes a constructor.

slide-65
SLIDE 65

(co-)Yoneda embedding

CPS transform to get a functor: data Console a = Ask String (String -> a) | Tell String a deriving Functor

slide-66
SLIDE 66

Adding functors

So far, so good. What about modularity? Idea: combining languages can literally be a sum of functors.

slide-67
SLIDE 67

Adding functors

So far, so good. What about modularity? Idea: combining languages can literally be a sum of functors. data (f :+: g) a = Inl f a | Inr g a instance (Functor f, Functor g) => Functor (f :+: g) where fmap k (Inl fa) = fmap k fa fmap k (Inr ga) = fmap k ga

slide-68
SLIDE 68

For instance we can break Console up data Ask a = Ask String (a -> String) deriving Functor data Tell a = Tell String a deriving Functor

slide-69
SLIDE 69

For instance we can break Console up data Ask a = Ask String (a -> String) deriving Functor data Tell a = Tell String a deriving Functor type Console = Ask :+: Tell

slide-70
SLIDE 70

Free monads

We can make a monad out of any functor, using the “free monad” construction.2

2For more details, see Ken Scambler’s YLJ14 talk: “Run free with the

monads: Free Monads for fun and profit”

slide-71
SLIDE 71

Free monads

We can make a monad out of any functor, using the “free monad” construction.2 What is it, and where does it come from?

2For more details, see Ken Scambler’s YLJ14 talk: “Run free with the

monads: Free Monads for fun and profit”

slide-72
SLIDE 72

Free monads

We can make a monad out of any functor, using the “free monad” construction.2 What is it, and where does it come from? It turns out that being “free” is closely connected to our idea of

  • perations as data types.

2For more details, see Ken Scambler’s YLJ14 talk: “Run free with the

monads: Free Monads for fun and profit”

slide-73
SLIDE 73

Can we use “operations into constructors” to turn the Monad class into a data type?

slide-74
SLIDE 74

Can we use “operations into constructors” to turn the Monad class into a data type? Yes!

slide-75
SLIDE 75

Can we use “operations into constructors” to turn the Monad class into a data type? Yes! class Monad m where return :: a -> m a join :: m (m a) -> m a

slide-76
SLIDE 76

Can we use “operations into constructors” to turn the Monad class into a data type? Yes! class Monad m where return :: a -> m a join :: m (m a) -> m a data Free f a where Return :: a -> Free f a Join :: f (Free f a) -> Free f a

slide-77
SLIDE 77

data Free f a = Return a | Join (f (Free f a)) Free f a is a monad whenever f is a functor.

slide-78
SLIDE 78

data Free f a = Return a | Join (f (Free f a)) Free f a is a monad whenever f is a functor. It’s the “naïve free monad.”

slide-79
SLIDE 79

data Free f a = Return a | Join (f (Free f a)) Free f a is a monad whenever f is a functor. It’s the “naïve free monad.” What exactly are we claiming when we say it’s the “free monad on f”?

slide-80
SLIDE 80

A small digression into abstract nonsense

slide-81
SLIDE 81

Categories have objects, and morphisms between objects.

slide-82
SLIDE 82

Haskell types

Haskell types form a category: a morphism between two types is just a function. f :: a -> b

slide-83
SLIDE 83

Functors

Functors between Haskell types forms a category, where a morphisms between two functors is a natural transformation. A natural transformation is just a polymorphic function n :: f a -> g a

slide-84
SLIDE 84

Monads

Monads on Haskell types also form a category.

slide-85
SLIDE 85

Monads

Monads on Haskell types also form a category. A monad is a functor with some additional structure (>>= and return).

slide-86
SLIDE 86

Monads

Monads on Haskell types also form a category. A monad is a functor with some additional structure (>>= and return). So a morphism between monads will be a natural transformation preserving that structure: n . (f >=> g) = (n . f) >=> (n . g) n . return = return where (f >=> g) a = f a >>= g

slide-87
SLIDE 87

The correct notion of an “interpretation” of a monad M1 into another monad M2 is just a monad morphism M1 → M2.

slide-88
SLIDE 88

The correct notion of an “interpretation” of a monad M1 into another monad M2 is just a monad morphism M1 → M2. In fact this is important for what will follow.

slide-89
SLIDE 89

The correct notion of an “interpretation” of a monad M1 into another monad M2 is just a monad morphism M1 → M2. In fact this is important for what will follow. It means if we write a program in the free monad, then translate, we can be sure we continue to obey the monad laws. Otherwise, we’ll lose predictability i.e. the ability to reason about

  • ur code.
slide-90
SLIDE 90

Adjunctions

Usually in Haskell, functors go from Hask to Hask. But in general, functors go from one category C to another D.

slide-91
SLIDE 91

Adjunctions

Usually in Haskell, functors go from Hask to Hask. But in general, functors go from one category C to another D. An adjunction is the name for the situation where we have functors L :: C → D, R :: D → C. satisfying D(Lc, d) ∼ = C(c, Rd) for all objects c in C and d in D. C(c1, c2) = the collection of C morphisms from c1 to c2. D(d1, d2) = the collection of D morphisms from d1 to d2.

slide-92
SLIDE 92

When R is an inclusion of C into D we write F = L and say

◮ F is the “free D functor on C”

slide-93
SLIDE 93

When R is an inclusion of C into D we write F = L and say

◮ F is the “free D functor on C” ◮ Fc the “free D on c”.

slide-94
SLIDE 94

When R is an inclusion of C into D we write F = L and say

◮ F is the “free D functor on C” ◮ Fc the “free D on c”.

slide-95
SLIDE 95

When R is an inclusion of C into D we write F = L and say

◮ F is the “free D functor on C” ◮ Fc the “free D on c”.

This gives us a simple description of all D morphisms out of Fc: D(Fc, d) ∼ = C(c, d)

slide-96
SLIDE 96

When R is an inclusion of C into D we write F = L and say

◮ F is the “free D functor on C” ◮ Fc the “free D on c”.

This gives us a simple description of all D morphisms out of Fc: D(Fc, d) ∼ = C(c, d) they just correspond to the C morphisms out of c.

slide-97
SLIDE 97

In our case, our adjunction looks like Mnd(Free F, M) ∼ = Func(F, M)

slide-98
SLIDE 98

In our case, our adjunction looks like Mnd(Free F, M) ∼ = Func(F, M) If F breaks up into a sum of functors . . . F = F1 + · · · + Fk,

slide-99
SLIDE 99

In our case, our adjunction looks like Mnd(Free F, M) ∼ = Func(F, M) If F breaks up into a sum of functors . . . F = F1 + · · · + Fk, then Mnd(Free (F1 + · · · + Fk), M) ∼ = Func(F1 + · · · + Fk, M) ∼ = Func(F1, M) × · · · × Func(Fk, M)

slide-100
SLIDE 100

In other words to interpret a program written in the free monad of a sum of languages F1, . . . , Fk

slide-101
SLIDE 101

In other words to interpret a program written in the free monad of a sum of languages F1, . . . , Fk we just have to give a (functor) interpretation for each language.

slide-102
SLIDE 102

In other words to interpret a program written in the free monad of a sum of languages F1, . . . , Fk we just have to give a (functor) interpretation for each language. So this is the sense in which we have modular interpreters.

slide-103
SLIDE 103

Example

data Ask a = Ask String (String -> a) data Tell a = Tell String a data Lookup k v a = Lookup k ((Maybe v) -> a)

slide-104
SLIDE 104

checkQuota :: Free (Ask :+: (Lookup String Int) :+: Tell) () checkQuota = do name <- Join . Inl $ Ask "What's your name" Return quota <- Join . Inr . Inl $ Lookup name Return Join . Inr . Inr $ Tell ( "Hi " ++ name ++ ", your quota is " ++ show (fromMaybe 0 quota) ) (Return ())

slide-105
SLIDE 105

checkQuota :: Free (Ask :+: (Lookup String Int) :+: Tell) () checkQuota = do name <- Join . Inl $ Ask "What's your name" Return quota <- Join . Inr . Inl $ Lookup name Return Join . Inr . Inr $ Tell ( "Hi " ++ name ++ ", your quota is " ++ show (fromMaybe 0 quota) ) (Return ()) The injections Inl and Inr break our stability goal!

slide-106
SLIDE 106

checkQuota :: Free (Ask :+: (Lookup String Int) :+: Tell) () checkQuota = do name <- Join . Inl $ Ask "What's your name" Return quota <- Join . Inr . Inl $ Lookup name Return Join . Inr . Inr $ Tell ( "Hi " ++ name ++ ", your quota is " ++ show (fromMaybe 0 quota) ) (Return ()) The injections Inl and Inr break our stability goal! We’ll have to modify the embeddings everywhere if we want to add another language.

slide-107
SLIDE 107

Solution: polymorphism!

Introduce a type class to manage the injections for us class f :<: g where inj :: f a -> g a

slide-108
SLIDE 108

Solution: polymorphism!

Introduce a type class to manage the injections for us class f :<: g where inj :: f a -> g a Add instances to capture composition of injections. instance f :<: f where ... instance f :<: (f :+: g) where ... instance (f :<: h) => f :<: (g :+: h) where ...

slide-109
SLIDE 109

A polymorphic injection function

inject :: (f :<: g) => f (Free g a) -> Free g a inject = Join . inj

slide-110
SLIDE 110

A polymorphic injection function

inject :: (f :<: g) => f (Free g a) -> Free g a inject = Join . inj Rewrite our program in terms of smart constructors ask :: Ask :<: f => String -> Free f String ask s = inject $ Ask s Return

slide-111
SLIDE 111

Invisible abstraction

checkQuota :: Free (Ask :+: Lookup String Int :+: Tell) () checkQuota = do name <- ask "What is your name?" quota <- lookup name tell $ "Hi " ++ name ++ ", your quota is " ++ (show (fromMaybe 0 quota))

slide-112
SLIDE 112

Invisible abstraction

checkQuota :: (Ask :<: f, Lookup String Int :<: f, Tell :<: f) => Free f () checkQuota = do name <- ask "What is your name?" quota <- lookup name tell $ "Hi " ++ name ++ ", your quota is " ++ (show (fromMaybe 0 quota))

slide-113
SLIDE 113

Interpretation

class (Functor f, Monad m) => Interpret f where intp :: f a -> m a instance (Interpret f m, Interpret g m) => Interpret (f :+: g) m where intp (Inl fa) = intp fa intp (Inr ga) = intp ga interpret :: Interpret f m => Free f a => m a interpret (Return a) = return a interpret (Join fa) = join $ intp (interpret <$> fa)

slide-114
SLIDE 114

Interpretation

class (Functor f, Monad m) => Interpret f where intp :: f a -> m a instance (Interpret f m, Interpret g m) => Interpret (f :+: g) m where intp (Inl fa) = intp fa intp (Inr ga) = intp ga interpret :: Interpret f m => Free f a => m a interpret (Return a) = return a interpret (Join fa) = join $ intp (interpret <$> fa) interpret is a monad morphism.

slide-115
SLIDE 115

type MyMonad = ReaderT (Map String Int) IO instance Interpret Ask MyMonad where ... instance Interpret Tell MyMonad where ... instance Interpret (Lookup String Int) MyMonad where ...

slide-116
SLIDE 116

Let’s run our program in ReaderT (Map String Int) IO. > runReaderT (interpret checkQuota) $ [ ("Stu", 33), ("Ann", 55) ] What's your name? Ann Hi Ann, your quota is 55

slide-117
SLIDE 117

Specification-side effects

It’s not clear how we could easily add effects like early termination to our program: we’d need to create a FreeError f. i.e. an analogy of Free f but for MonadFree, obeying the appropriate laws and satisfying a universal property.

slide-118
SLIDE 118

Other binding constructs

◮ If we wanted to use comonads instead, we could use cofree

comonads.3 We’d have to invent “codata types à la carte”.

3See Dave Laing’s YLJ15 talk “Cofun with Cofree Monads”

slide-119
SLIDE 119

Other binding constructs

◮ If we wanted to use comonads instead, we could use cofree

comonads.3 We’d have to invent “codata types à la carte”.

◮ If we wanted arrows, it’s not clear what to do.

3See Dave Laing’s YLJ15 talk “Cofun with Cofree Monads”

slide-120
SLIDE 120

Other binding constructs

◮ If we wanted to use comonads instead, we could use cofree

comonads.3 We’d have to invent “codata types à la carte”.

◮ If we wanted arrows, it’s not clear what to do.

3See Dave Laing’s YLJ15 talk “Cofun with Cofree Monads”

slide-121
SLIDE 121

Other binding constructs

◮ If we wanted to use comonads instead, we could use cofree

comonads.3 We’d have to invent “codata types à la carte”.

◮ If we wanted arrows, it’s not clear what to do.

Although free arrows ought to exist, we don’t yet have a Haskell implementation.

3See Dave Laing’s YLJ15 talk “Cofun with Cofree Monads”

slide-122
SLIDE 122

Limitations

◮ It’s slow.

slide-123
SLIDE 123

Limitations

◮ It’s slow. ◮ It’s complex. We have considerable boilerplate: injections,

smart-constructors, interpreters.

slide-124
SLIDE 124

Limitations

◮ It’s slow. ◮ It’s complex. We have considerable boilerplate: injections,

smart-constructors, interpreters.

◮ {-# LANGUAGE TypeOperators

#-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE OverlappingInstances #-}

slide-125
SLIDE 125

Limitations

◮ It’s slow. ◮ It’s complex. We have considerable boilerplate: injections,

smart-constructors, interpreters.

◮ {-# LANGUAGE TypeOperators

#-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE DeriveFunctor #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE OverlappingInstances #-}

◮ No backtracking in Haskell’s type solver means for :<: we can

  • nly have nesting on one side of :+:
slide-126
SLIDE 126

Solution 2: Operations remain functions

slide-127
SLIDE 127

Oleg Kiselyov, Typed Tagless Final Interpreters, 2012 Motto Let the language do the work

slide-128
SLIDE 128

A language is a type class parametrized by a data constructor.

slide-129
SLIDE 129

A language is a type class parametrized by a data constructor. i.e. the parameter has kind * -> *.

slide-130
SLIDE 130

A language is a type class parametrized by a data constructor. i.e. the parameter has kind * -> *. class Console r where ask :: String -> r String tell :: String -> r ()

slide-131
SLIDE 131

A language is a type class parametrized by a data constructor. i.e. the parameter has kind * -> *. class Console r where ask :: String -> r String tell :: String -> r () class KeyVal k v r where lookup :: k -> r (Maybe v) set :: k -> v -> r ()

slide-132
SLIDE 132

An interpreter is a type with an instance. instance MonadIO m => Console m where ask s = putStrLn s >> getLine tell = putStrLn

slide-133
SLIDE 133

An interpreter is a type with an instance. instance MonadIO m => Console m where ask s = putStrLn s >> getLine tell = putStrLn instance (Ord k, MonadState (Map k v) m) => KeyVal k v m where lookup = gets . DM.lookup set k v = modify $ DM.insert k v

slide-134
SLIDE 134

Adding languages simply means adding constraints.

slide-135
SLIDE 135

It’s easy to split languages.

slide-136
SLIDE 136

It’s easy to split languages. class Lookup k v where lookup :: k -> r (Maybe v) class Set k v r where set :: k -> v -> r ()

slide-137
SLIDE 137

It’s easy to split languages. class Lookup k v where lookup :: k -> r (Maybe v) class Set k v r where set :: k -> v -> r () . . . and to combine them. type KeyVal k v r = (Lookup k v r, Set k v r) Requires -XConstraintKinds.

slide-138
SLIDE 138

So our languages are modular.

slide-139
SLIDE 139

We have modular interpreters just by adding new instances for our type.

slide-140
SLIDE 140

For a monadic computation, just add a Monad constraint.

slide-141
SLIDE 141

For a monadic computation, just add a Monad constraint. getQuota :: ( Console r , Lookup String Int r , Monad r ) => r () getQuota = do name <- ask "What's your name?" quota <- fromMaybe 0 <$> lookup name tell $ "Hi " + name + ", your quota is " ++ show quota

slide-142
SLIDE 142

Adding effects to the specification is easy. Just add a different constraint in place of Monad.

slide-143
SLIDE 143

Adding effects to the specification is easy. Just add a different constraint in place of Monad. changePwd :: ( Console r , KeyVal String String r , MonadThrow r ) => r () changePwd = do n <- ask "What's your name?" p <- ask "What's your password?" matches <- (== Just p) <$> lookup n unless matches $ throwM WrongPassword np <- ask "Enter new password" np2 <- ask "Re-enter new password" unless (np == np2) $ throwM PasswordsDidNotMatch put n np tell "Password successfully updated"

slide-144
SLIDE 144

To have a comonadic or arrowized computation, just add the relevant constraint.

slide-145
SLIDE 145

Interpretation is the identity function.

slide-146
SLIDE 146

Interpretation is the identity function. i.e. just select a type (that has an instance for all constraints).

slide-147
SLIDE 147

Let’s run our program in StateT (Map String String) IO. > runStateT changePwd $ fromList [ ("anne", "pwd123") , ("mark", "p@ssw0rd") ] What's your name? mark What's your password? p@ssw0rd Enter new password 1337 Re-enter new password 1337 ((),fromList [("sue","pwd123"),("mark","1337")])

slide-148
SLIDE 148

Comparison

slide-149
SLIDE 149

À la carte Free monads over sums of functors. Interpretation is a monad morphism, assembled in a modular fashion from interpretations (natural transformations) for each component language.

slide-150
SLIDE 150

Typed tagless Languages are (higher-kinded) type classes. We combine languages by adding constraints. Interpretations are types with the necessary instances.

slide-151
SLIDE 151

Compared to à la carte, the tagless approach

◮ has minimal runtime overhead

slide-152
SLIDE 152

Compared to à la carte, the tagless approach

◮ has minimal runtime overhead ◮ has no need for boilerplate

slide-153
SLIDE 153

Compared to à la carte, the tagless approach

◮ has minimal runtime overhead ◮ has no need for boilerplate ◮ requires fewer language extensions

slide-154
SLIDE 154

Compared to à la carte, the tagless approach

◮ has minimal runtime overhead ◮ has no need for boilerplate ◮ requires fewer language extensions

slide-155
SLIDE 155

Compared to à la carte, the tagless approach

◮ has minimal runtime overhead ◮ has no need for boilerplate ◮ requires fewer language extensions

On the other hand, some things are easier with the free monad approach e.g.

◮ free monads allow stepping through instruction-by-instruction

slide-156
SLIDE 156

Related work

◮ Stacked FreeT.

slide-157
SLIDE 157

Related work

◮ Stacked FreeT. ◮ Alternative (more efficient) implementations of free monads

e.g. van Laarhooven free monad, “reflection without remorse”

slide-158
SLIDE 158

Related work

◮ Stacked FreeT. ◮ Alternative (more efficient) implementations of free monads

e.g. van Laarhooven free monad, “reflection without remorse”

◮ Algebraic effects — avoid a monad stack altogether

e.g. "Freer monads, more extensible effects"

slide-159
SLIDE 159

Related work

◮ Stacked FreeT. ◮ Alternative (more efficient) implementations of free monads

e.g. van Laarhooven free monad, “reflection without remorse”

◮ Algebraic effects — avoid a monad stack altogether

e.g. "Freer monads, more extensible effects"

◮ Extensible data types

slide-160
SLIDE 160

Related work

◮ Stacked FreeT. ◮ Alternative (more efficient) implementations of free monads

e.g. van Laarhooven free monad, “reflection without remorse”

◮ Algebraic effects — avoid a monad stack altogether

e.g. "Freer monads, more extensible effects"

◮ Extensible data types

slide-161
SLIDE 161

Related work

◮ Stacked FreeT. ◮ Alternative (more efficient) implementations of free monads

e.g. van Laarhooven free monad, “reflection without remorse”

◮ Algebraic effects — avoid a monad stack altogether

e.g. "Freer monads, more extensible effects"

◮ Extensible data types

see the compdata package and “Typed tagless final interpreters”.

slide-162
SLIDE 162

References

slide-163
SLIDE 163

Swierstra, Wouter. “Data types à la carte.” Journal of functional programming 18.04 (2008): 423-436. Bahr, Patrick, and Hvitved, Tom. “Compositional data types.” Proceedings of the seventh ACM SIGPLAN workshop on Generic

  • programming. ACM, 2011.

Kiselyov, Oleg. “Typed tagless final interpreters.” Generic and Indexed Programming. Springer Berlin Heidelberg, 2012. 130-174.