## First-order, deBruijn Index Based Interpreter This section is my own presentation, and explanation, of [Oleg's LLC interpreter](http://okmij.org/ftp/tagless-final/course/LinearLC.hs). First, I will change the presentation of the language to use deBruijn indices: ```haskell -- show Grammar for LLC with deBruijn Indices m ::= z | s m | llam m | m ^ m ``` and 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 :: B ``` Note that in rule `lvar-s`, `B` can be either `_` or some type `A`. I will use well-typed haskell terms: ```haskell m :: repr hi ho a ``` to represent the judgement: ``` Di \ Do |- m :: A ``` The following is a direct transcription of the above typing rules into Haskell types. ```active haskell -- /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. ```active haskell -- /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. ```haskell 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`. ```haskell 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`: ```active haskell -- /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" ```