Dependently Typed Functional Programming withIdris
Lecture 2: Embedded Domain Specific Languages
Edwin BradyUniversity of St Andrews
@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
Top Related