1 of 46

The Dialectics of Type-Level Programming; or How I Learned To Love Values

2 of 46

¡Hola!

3 of 46

thank you

4 of 46

Soy Aaron Levin

Recommendations @

MSc. Mathematics

@aaronmblevin

5 of 46

today

we are going to do great things

6 of 46

  1. DSLs
  2. Type-Level DSLs
  3. Example
  4. A Type-Level DSL for Event Processing
  5. The Pains of Being Pure at Heart

7 of 46

DSL

8 of 46

Domain

Specific

Language

GNU make

Verilog

9 of 46

eDSL

10 of 46

embedded

Domain

Specific

Language

val a = actor(new Act {

become {

case "Lambda" ⇒ sender() ! "World"

}})

"Lambda World" must haveSize(11)�"Lambda World" must startWith("Lambda")�"Lambda World" must endWith("World")

​

main = do� r <- runHaxl env $ do� likes <- getObject "lambda/world"� mapM getObject (likeIds likes)

​

11 of 46

eDSL

Value

Domain-Specific

Meaning

combinators

" " must have("sword")�" " must startWith("flames")�" " must endWith("flames")�

​

combinators

12 of 46

eDSLs are not extensible

​

:( :( :(

13 of 46

Type-Level DSL

14 of 46

Type-Level DSL

Value

Domain-Specific

Meaning

combinators

Type

Value

Type Class resolution

15 of 46

Type-Level DSLs are

extensible

16 of 46

example

Servant

(haskell)

17 of 46

Example: Servant

type HackageAPI =� -- GET /user� "user" :> Get '[JSON] [UserSummary]� -- GET /user/:username� :<|> "user" :> Capture "username" Username :> Get '[JSON] UserDetailed� :<|> "packages" :> Get '[JSON] [Package]

Type Class resolution

(value)

Haskell Client

(value)

Web Server

(value)

Mock Server

18 of 46

let’s build a Type-Level DSL

together

19 of 46

The Problem: Heterogeneous Event Sink

Event sink

“data lake”

event: play

event: pause

event: click

20 of 46

The Problem: Heterogeneous Event Sink

Event sink

event: play

event: pause

event: click

{ “name”: “click”,� “payload”: { ... }�}

{ “name”: “play”,� “payload”: { ... }�}

{ “name”: “pause”,� “payload”: { ... }�}

{ “name”: “click”,

“payload”: {

“user”: “lambda”,� “page:” “track/world”

}

}

{ “name”: “play”,

“payload”: {

“user”: “lambda”,� “track:” 420

}

}

21 of 46

The Problem: Heterogeneous Event Sink

Our Event Sink must:

  1. Accept events of different types
  2. Dispatch on event name
  3. Parse payload into a type depending on the name

22 of 46

Our Type-Level DSL: Three Steps

Haskell

type Events = � ( Click, � ( Play, � ( Pause,� EndOfList) ) )

​

Scala

type Events = � ( Click,� ( Play, � ( Pause,� EndOfList) ) )

  1. Encoding lists of types as embedded tuples
  2. A typeclass to interpret Events and generate the value “click, play, pause”
  3. A handleEvent method using a typeclass to dispatch and parse an event.

23 of 46

Encoding Lists of Types as Embedded Tuples

type EndOfList = Unit

[Int, String, Char] → (Int, (String, (Char, EndOfList))))

24 of 46

let’s begin!

source: http://jjjjjjjjjjohn.tumblr.com/

25 of 46

A little bit about typeclass induction

26 of 46

induction: base case

​

​

implicit val baseCaseName = new Named[EndOfList] { � val name = “”�}

27 of 46

induction step

type Events = (Click, (Play, (Pause, EndOfList)))�type Events = (� (Click, Named[(Click, (Play, (Pause, EndOfList)))]�

(Play, Named[(Play,(Pause, EndOfList))]�

(Pause, Named[(Pause, EndOfList)]�

EndOfList))) Named[EndOfList]

implicit val

implicit def

implicit def

implicit def

28 of 46

dynamic dispatch,

statically!

29 of 46

Recap: interpreter #1

type Events = (Click, (Play, (Pause, EndOfList)))��trait Named[E] { val name: String }�implicit val namedClick: Named[Click]�implicit val namedPlay: Named[Play]�implicit val namedPause: Named[Pause]�implicit val namedBaseCase: Named[EndOfList]�implicit def induction[E](implicit t: Named[Tail]): Named[(E,Tail)]

$ getNamed[Events] = “click, play, pause,”

30 of 46

Recap: interpreter #2

type Events = (Click, (Play, (Pause, EndOfList)))��trait HandleEvents[E] {� type Out� def handleEvent(s: String, String): Either[String, Out] �}�implicit def induction[E](implicit n: Named[E], f: FromString[E], t: HandleEvents[Tail]): HandleEvents[(E,Tail)]

$ handleEvent[Events]("play", "lambdaworld\t123") =� “Right(Left(Right(Play(lambdaworld,123))))”

​

31 of 46

can we have it all?

32 of 46

almost?....!

33 of 46

No instance for (FromString Int) arising from a use of ‘handleEvent’

34 of 46

error: could not find implicit value for parameter names:

HandleEvents[Events]

35 of 46

handleEvent :: Handle events => Proxy events -> String -> String -> Out events

handleEvent :: Handle events => String -> String -> Out events

Couldn't match type ‘Out events’ with ‘Out events0’�NB: ‘Out’ is a type function, and may not be injective�The type variable ‘events0’ is ambiguous�Expected type: String -> String -> Out events� Actual type: String -> String -> Out events0�In the ambiguity check for the type signature for ‘handle’:� handle :: forall (k :: BOX) (events :: k).�…….�…….�…….

36 of 46

Dialectics

37 of 46

Dialectics

DSLs

Type-Level DSLs

contradiction

Extensibility

38 of 46

Dialectics

Runtime

Bugs

Compiler

Bugs

❤️

Interpreting Values

​

contradiction?

39 of 46

Recap

  • Type-Level DSLs use typeclass resolution to crawl type-level lists and generate values.
  • Type-Level DSLs are extensible
  • Type-Level DSLs use core language constructs
  • Type-Level DSLs availalbe in Scala *and* Haskell
  • Type-Level DSLs are found in the wild: Servant (haskell), Shapeless (scala)
  • But compiler errors can be unforgiving
  • Until we have Idris, Type-Level DSLs are our only hope!

40 of 46

thank you

41 of 46

(everything after this slide are rejected slides)

42 of 46

❤️ Values

43 of 46

Dialectics

Object

Function

???

contradiction

44 of 46

Dialectics

Thesis

Antithesis

Synthesis

contradiction

45 of 46

Type-Level DSL

Value

Domain-Specific

Meaning

combinators

Type

???

compiler

46 of 46

induction

be like