First-order, deBruijn Index Based Interpreter
This section is my own presentation, and explanation, of Oleg's LLC interpreter. First, I will change the presentation of the language to use deBruijn indices:
-- show Grammar for LLC with deBruijn Indices
m ::= z | s m | llam m | m ^ mand slightly modify the algorithmic typing rules above to use an
ordered list for the linear context (actually an ordered list of Maybe
types, where Just is implicit and _ is Nothing), instead of a
set:
------------------------ (lvar-z)
A, D \ _, D |- z :: A
Di \ Do |- m :: A
--------------------------- (lvar-s)
B, Di \ B, Do |- s m :: A
A, Di \ _, Do |- m :: B
----------------------------- (-<>I)
Di \ Do |- lam m :: A -<> B
Di \ D |- m :: A -<> B D \ Do |- n :: A
----------------------------------------------- (-<>E)
Di \ Do |- m ^ n :: BNote that in rule lvar-s, B can be either _ or some type A.
I will use well-typed haskell terms:
m :: repr hi ho ato represent the judgement:
Di \ Do |- m :: AThe following is a direct transcription of the above typing rules into Haskell types.
-- /show
{-# LANGUAGE
NoMonomorphismRestriction,
FlexibleInstances,
MultiParamTypeClasses,
TypeOperators
#-}
-- show
-- type level Maybe for representing elements of the linear context
newtype F a = F a
data U = Used
class Linear repr where
z :: repr (F a, h) (U, h) a
s :: repr hi ho a -> repr (any, hi) (any, ho) a
llam :: repr (F a, hi) (U, ho) b -> repr hi ho (a -> b)
(<^>) :: repr hi h (a -> b) -> repr h ho a -> repr hi ho b
t0 = llam $ llam $ s z <^> z
-- inferred type of t0 :: Linear repr => repr h h ((a -> b) -> a -> b)
-- following won't type check
-- t1 = llam $ llam $ s z <^> z <^> z
main = putStrLn "ok"Note that the return type of llam contains a Haskell function. Although the syntax uses deBruijn notation, linear functions are being represented by Haskell functions; so the llam method of any instance of Linear will have to create a Haskell function.
Another interesting observation about the code above is that Haskell's type checker is enforcing linearity for us; as of yet, there is no instance for the class Linear.
In order to have an instance for Linear which lets us
evaluate LLC expressions, we need a suitable type, call it
R, to concretely represent our LLC terms. Since the language uses deBruijn indices, we will need to have an
explicit representation of the linear context around at runtime.
-- /show
{-# LANGUAGE
NoMonomorphismRestriction,
FlexibleInstances,
MultiParamTypeClasses,
TypeOperators
#-}
-- type level Maybe for representing elements of the linear context
newtype F a = F a
data U = Used
class Linear repr where
z :: repr (F a, h) (U, h) a
s :: repr hi ho a -> repr (x, hi) (x, ho) a
llam :: repr (F a, hi) (U, ho) b -> repr hi ho (a -> b)
(<^>) :: repr hi h (a -> b) -> repr h ho a -> repr hi ho b
-- show
-- Typed and tagless evaluator
-- object term ==> metalanguage value
newtype R hi ho a = R {unR :: hi -> (a, ho)}
instance Linear R where
z = R $ \(F x, h) -> (x, (Used, h))
s v = R $ \(any, hi) ->
let (x, ho) = unR v hi
in (x, (any, ho))
-- !!! Following does not work since vo is not in scope !!!
--
llam e = R $ \hi -> (f hi, vo)
where f hi x = let (v, vo) = unR e (F x,hi)
in v
m1 <^> m2 = R $ \hi -> let (v1, h) = unR m1 hi
(v2, ho) = unR m2 h
in (v1 v2, ho) As noted above, this code doesn't type check since the output linear
context needed by llam is unknown; it won't be computed until the
function is actually applied. However, there is a
way to get around this problem; the input context and the type of the
output context (which is statically determined) are enough to generate
the actual output context. A type class can
be used to generate an output context from an input context and the known output context type.
class HiHo hi ho where
hiho :: hi -> ho
instance HiHo () () where
hiho = id
instance HiHo hi ho => HiHo (F a , hi) (F a , ho) where
hiho (x, hi) = (x, hiho hi)
instance HiHo hi ho => HiHo (U , hi) (U , ho) where
hiho (x, hi) = (x, hiho hi)
instance HiHo hi ho => HiHo (F a, hi) (U, ho) where
hiho (x, hi) = (Used, hiho hi)In order to let llam use hiho, its type-level linear contexts must be exposed. However, I don't want to add these to Linear since neither z nor <^> mention exactly two type-level linear contexts; therefore, I will add another class, LinearLam,
just for llam.
class Linear repr where
z :: repr (F a , h) (U , h) a
s :: repr hi ho a -> repr (x , hi) (x , ho) a
(<^>) :: repr hi h (a -> b) -> repr h ho a -> repr hi ho b
class LinearLam repr hi ho where
llam :: repr (F a , hi) (U , ho) b -> repr hi ho (a -> b)I can now write correct instances of Linear and LinearLam for R as well as an evaluator for terms of type R:
-- /show
{-# LANGUAGE
NoMonomorphismRestriction,
FlexibleInstances,
MultiParamTypeClasses,
TypeOperators
#-}
-- type level Maybe for representing elements of the linear context
newtype F a = F a
data U = Used
class HiHo hi ho where
hiho :: hi -> ho
instance HiHo () () where
hiho = id
instance HiHo hi ho => HiHo (F a , hi) (F a , ho) where
hiho (x, hi) = (x, hiho hi)
instance HiHo hi ho => HiHo (U , hi) (U , ho) where
hiho (x, hi) = (x, hiho hi)
instance HiHo hi ho => HiHo (F a, hi) (U, ho) where
hiho (x, hi) = (Used, hiho hi)
class Linear repr where
z :: repr (F a , h) (U , h) a
s :: repr hi ho a -> repr (x , hi) (x , ho) a
(<^>) :: repr hi h (a -> b) -> repr h ho a -> repr hi ho b
class LinearLam repr hi ho where
llam :: repr (F a , hi) (U , ho) b -> repr hi ho (a -> b)
-- show
-- Typed and tagless evaluator
-- object term ==> metalanguage value
newtype R hi ho a = R {unR :: hi -> (a, ho)}
instance Linear R where
z = R $ \(F x, h) -> (x, (Used, h))
s v = R $ \(any, hi) ->
let (x, ho) = unR v hi
in (x, (any, ho))
m1 <^> m2 = R $ \hi -> let (v1, h) = unR m1 hi
(v2, ho) = unR m2 h
in (v1 v2, ho)
instance HiHo hi ho => LinearLam R hi ho where
llam e = R $ \hi -> (f hi, hiho hi)
where f hi x = let (v, _) = unR e (F x, hi)
in v
-- The implementation of lam shows that the value of lam, which is
-- f hi, is the closure of the (input) environment in which
-- lam was produced.
eval :: R () () a -> a
eval e = fst $ unR e () -- Eval in the empty environment
t = llam $ llam $ s z <^> z
main = do
putStrLn $ eval (t <^> llam z) "I was passed to a real function."
putStrLn "ok"