Dependently Typed Functional Programming with IdrisDependently Typed Functional Programming with...

Post on 23-Sep-2020

6 views 0 download

Transcript of Dependently Typed Functional Programming with IdrisDependently Typed Functional Programming with...

Dependently Typed Functional Programming withIdris

Lecture 2: Embedded Domain Specific Languages

Edwin BradyUniversity of St Andrews

ecb10@st-andrews.ac.uk

@edwinbrady

DSLs in Idris

Idris aims to support the implementation of verified domainspecific languages. To illustrate this, we begin with an interpreterfor the simply typed λ-calculus.

First Attempt

Expressions

data Expr = Val Int

| Add Expr Expr

Evaluator

interp : Expr -> Int

interp (Val x) = x

interp (Add l r) = interp l + interp r

First Attempt

Expressions

data Expr = Val Int

| Add Expr Expr

Evaluator

interp : Expr -> Int

interp (Val x) = x

interp (Add l r) = interp l + interp r

First Attempt

Expressions

data Expr = Val Int

| Var String

| Add Expr Expr

Evaluator

interp : List (String, Int) -> Expr -> Maybe Int

interp env (Val x) = x

interp env (Var n) = case lookup n env of

Nothing => Nothing

Just val => val

interp env (Add l r) = interp env l + interp env r

First Attempt

Expressions

data Expr = Val Int

| Var String

| Add Expr Expr

Evaluator

interp : List (String, Int) -> Expr -> Maybe Int

interp env (Val x) = x

interp env (Var n) = case lookup n env of

Nothing => Nothing

Just val => val

interp env (Add l r) = interp env l + interp env r

Preliminaries

Types

data Ty = TyInt

| TyBool

| TyFun Ty Ty

Interpreting types

interpTy : Ty -> Type

interpTy TyInt = Int

interpTy TyBool = Bool

interpTy (TyFun s t) = interpTy s -> interpTy t

Preliminaries

Types

data Ty = TyInt

| TyBool

| TyFun Ty Ty

Interpreting types

interpTy : Ty -> Type

interpTy TyInt = Int

interpTy TyBool = Bool

interpTy (TyFun s t) = interpTy s -> interpTy t

Preliminaries

The Finite Sets

data Fin : Nat -> Type where

fO : Fin (S k)

fS : Fin k -> Fin (S k)

Example: Bounds Safe Vector Lookup

total

index : Fin n -> Vect a n -> a

index fO (x::xs) = x

index (fS k) (x::xs) = index k xs

Preliminaries

The Finite Sets

data Fin : Nat -> Type where

fO : Fin (S k)

fS : Fin k -> Fin (S k)

Example: Bounds Safe Vector Lookup

total

index : Fin n -> Vect a n -> a

index fO (x::xs) = x

index (fS k) (x::xs) = index k xs

Preliminaries

We can represent variables as a de Bruijn index

Nameless

A number, counting binders since the variable was bound

λxy .x + y =⇒ λxy .1 + 0

If there are n variables:

Fin n represents a bounded de Bruijn indexindex i G is a bounds safe lookup of variable i in context G.

Preliminaries

We can represent variables as a de Bruijn index

Nameless

A number, counting binders since the variable was bound

λxy .x + y =⇒ λxy .1 + 0

If there are n variables:

Fin n represents a bounded de Bruijn indexindex i G is a bounds safe lookup of variable i in context G.

Preliminaries

Environments

using (G : Vect Ty n)

data Env : Vect Ty n -> Type where

Nil : Env Nil

(::) : interpTy a -> Env G -> Env (a :: G)

data HasType : (i : Fin n) -> Vect Ty n -> Ty ->

Type where

stop : HasType fO (t :: G) t

pop : HasType k G t ->

HasType (fS k) (u :: G) t

Preliminaries

Environment Example

ctxt : Vect Ty (S (S O))

ctxt = [TyInt, TyBool]

env : Env ctxt

env = [42, True]

isBool : HasType (fS fO) ctxt TyBool

isBool = pop stop

Demonstration: the Interpreter

Syntax Overloading

dsl notation

dsl expr

lambda = Lam

variable = Var

index_first = stop

index_next = pop

An Effectful Problem (Haskell)

Evaluator

data Expr = Val Int | Add Expr Expr

eval :: Expr -> Int

eval (Val x) = x

eval (Add x y) = eval x + eval y

An Effectful Problem (Haskell)

Evaluator

data Expr = Val Int | Add Expr Expr

eval :: Expr -> Int

eval (Val x) = x

eval (Add x y) = eval x + eval y

An Effectful Problem (Haskell)

Evaluator with variables

data Expr = Val Int | Add Expr Expr

| Var String

type Env = [(String, Int)]

eval :: Expr -> ReaderT Env Maybe Int

eval (Val n) = return n

eval (Add x y) = liftM2 (+) (eval x) (eval y)

eval (Var x) = do env <- ask

val <- lift (lookup x env)

return val

An Effectful Problem (Haskell)

Evaluator with variables

data Expr = Val Int | Add Expr Expr

| Var String

type Env = [(String, Int)]

eval :: Expr -> ReaderT Env Maybe Int

eval (Val n) = return n

eval (Add x y) = liftM2 (+) (eval x) (eval y)

eval (Var x) = do env <- ask

val <- lift (lookup x env)

return val

An Effectful Problem (Haskell)

Evaluator with variables

data Expr = Val Int | Add Expr Expr

| Var String

type Env = [(String, Int)]

eval :: Expr -> ReaderT Env Maybe Int

eval (Val n) = return n

eval (Add x y) = liftM2 (+) (eval x) (eval y)

eval (Var x) = do env <- ask

val <- lift (lookup x env)

return val

An Effectful Problem (Haskell)

Evaluator with variables

data Expr = Val Int | Add Expr Expr

| Var String

type Env = [(String, Int)]

eval :: Expr -> ReaderT Env Maybe Int

eval (Val n) = return n

eval (Add x y) = liftM2 (+) (eval x) (eval y)

eval (Var x) = do env <- ask

val <- lift (lookup x env)

return val

An Effectful Problem (Haskell)

Evaluator with variables and random numbers

data Expr = Val Int | Add Expr Expr

| Var String

| Random Int

eval :: RandomGen g =>

Expr -> RandT g (ReaderT Env Maybe) Int

...

eval (Var x) = do env <- lift ask

val <- lift (lift (lookup x env))

return val

eval (Random x) = do val <- getRandomR (0, x)

return val

An Effectful Problem (Haskell)

Evaluator with variables and random numbers

data Expr = Val Int | Add Expr Expr

| Var String

| Random Int

eval :: RandomGen g =>

Expr -> RandT g (ReaderT Env Maybe) Int

...

eval (Var x) = do env <- lift ask

val <- lift (lift (lookup x env))

return val

eval (Random x) = do val <- getRandomR (0, x)

return val

An Effectful Problem (Haskell)

Evaluator with variables and random numbers

data Expr = Val Int | Add Expr Expr

| Var String

| Random Int

eval :: RandomGen g =>

Expr -> RandT g (ReaderT Env Maybe) Int

...

eval (Var x) = do env <- lift ask

val <- lift (lift (lookup x env))

return val

eval (Random x) = do val <- getRandomR (0, x)

return val

An Effectful Problem (Haskell)

Challenge — write the following:

dropReader :: RandomGen g =>

RandT g Maybe a ->

RandT g (ReaderT Env Maybe) a

commute :: RandomGen g =>

ReaderT (RandT g Maybe) a ->

RandT g (ReaderT Env Maybe) a

An Effectful Problem (Idris)

Instead, we could capture everything in one evaluation monad:

Eval monad

EvalState : Type

EvalState = (Int, List (String, Int))

data Eval a

= MkEval (EvalState -> Maybe (a, EvalState))

An Effectful Problem (Idris)

Eval operations

rndInt : Int -> Int -> Eval Int

get : Eval EvalState

put : EvalState -> Eval ()

An Effectful Problem (Idris)

Evaluator

eval : Expr -> Eval Int

eval (Val i) = return i

eval (Var x) = do (seed, env) <- get

lift (lookup x env)

eval (Add x y) = [| eval x + eval y |]

eval (Random upper) = do val <- rndInt 0 upper

return val

Embedded DSLs to the rescue!

Neither solution is satisfying!

Composing monads with transformers becomes hard tomanage

Order matters, but our effects are largely independent

Building one special purpose monad limits reuse

Instead:

We will build an extensible embedded domain specificlanguage (EDSL) to capture algebraic effects and theirhandlers

The Effect EDSL

The rest of this lecture is about an EDSL, Effect. We will see:

How to use effects

How to implement new effects and handlers

How Effect works

How to express extra-functional correctness properties usingEffect

Using Effects

Effectful programs

EffM : (m : Type -> Type) ->

List EFFECT -> List EFFECT -> Type -> Type

Eff : (Type -> Type) -> List EFFECT -> Type -> Type

run : Applicative m =>

Env m xs -> EffM m xs xs’ a -> m a

runPure : Env id xs -> EffM id xs xs’ a -> a

Using Effects

Some Effects

STATE : Type -> EFFECT

EXCEPTION : Type -> EFFECT

STDIO : EFFECT

FILEIO : Type -> EFFECT

RND : EFFECT

Examples

get : Eff m [STATE x] x

putM : y -> EffM m [STATE x] [STATE y] ()

raise : a -> Eff m [EXCEPTION a] b

putStr : String -> Eff IO [STDIO] ()

Using Effects

Some Effects

STATE : Type -> EFFECT

EXCEPTION : Type -> EFFECT

STDIO : EFFECT

FILEIO : Type -> EFFECT

RND : EFFECT

Examples

get : Eff m [STATE x] x

putM : y -> EffM m [STATE x] [STATE y] ()

raise : a -> Eff m [EXCEPTION a] b

putStr : String -> Eff IO [STDIO] ()

Demonstration