Accumulating Bindings
Total Page:16
File Type:pdf, Size:1020Kb
Accumulating bindings Sam Lindley The University of Edinburgh [email protected] Abstract but sums apparently require the full power of ap- plying a single continuation multiple times. We give a Haskell implementation of Filinski’s nor- malisation by evaluation algorithm for the computational In my PhD thesis I observed [14, Chapter 4] that by gen- lambda-calculus with sums. Taking advantage of extensions eralising the accumulation-based interpretation from a list to the GHC compiler, our implementation represents object to a tree that it is possible to use an accumulation-based language types as Haskell types and ensures that type errors interpretation for normalising sums. There I focused on are detected statically. an implementation using the state supported by the inter- Following Filinski, the implementation is parameterised nal monad of the metalanguage. The implementation uses over a residualising monad. The standard residualising Huet’s zipper [13] to navigate a mutable binding tree. Here monad for sums is a continuation monad. Defunctionalising we present a Haskell implementation of normalisation by the uses of the continuation monad we present the binding evaluation for the computational lambda calculus using a tree monad as an alternative. generalisation of Filinski’s binding list monad to incorpo- rate a tree rather than a list of bindings. (One motivation for using state instead of continuations 1 Introduction is performance. We might expect a state-based implementa- tion to be faster than an alternative delimited continuation- Filinski [12] introduced normalisation by evaluation for based implementation. For instance, Sumii and Kobay- the computational lambda calculus, using layered mon- ishi [17] claim a 3-4 times speed-up for state-based versus ads [11] for formalising name generation and for collecting continuation-based let-insertion. The results of my exper- bindings. He extended his algorithm to handle products and iments [14] suggest that in the case of sums it depends on sums, and outlined how to prove correctness using a Kripke the low-level implementation of the host language. For SM- logical relation. Filinski’s algorithm is parameterised by a L/NJ, which uses a CPS-based intermediate representation, residualising monad that is used for interpreting computa- the delimited continuations-based implementation outper- tions. formed the state-based implementation. The inefficiency of In the absence of sums he gives two concrete residual- the state-based implementation is in part due to it having ising monads: one a continuation monad and the other an to duplicate computation (something which seems hard to accumulation monad over a list of bindings, henceforth the avoid if we have sums). However, for MLton, which does binding list monad. He further shows how by using the in- not use a CPS-based intermediate representation, the state- ternal monad of the metalanguage it is possible to give cor- based implementation was faster. The implementation of responding algorithms for type-directed partial evaluation. delimited continuations in MLton is an order of magnitude If the metalanguage supports delimited continuations then slower than that of SML/NJ.) we can use shift and reset [9] in place of the continuation The main contributions of this article are rather modest: monad. If the metalanguage supports state then we can use a mutable list of bindings in place of the binding list monad. • A clean implementation of Filinski’s algorithm for nor- Filinski demonstrated how to extend his algorithm to malisation by evaluation for the compuational lambda support sums, but only in the case of the continuation calculus with sums in Haskell. monad (or delimited continuations). He writes: • A generalisation of the binding list monad from bind- Products could be added to an accumulation- ing lists to binding trees allowing it to be plugged into based interpretation without too much trouble, Filinski’s algorithm. 2 Implementation Carette et al [8] we use a type class, relying on parametricity to exclude so-called “exotic terms” [5]. Danvy et al [10] give an implementation of normalisa- class CompLam exp where tion by evaluation for call-by-name simply-typed lambda- lam :: (exp ! exp) ! exp calculus in Haskell. Input terms are represented as app :: exp ! exp ! exp closed Haskell expressions. The type-indexed reify and inl :: exp ! exp reflect functions are written as instances of a type class. inr :: exp ! exp The output terms are represented as typed higher-order ab- case_ :: stract syntax terms in normal form. As the types and struc- exp ! (exp ! exp) ! (exp ! exp) ! exp let_ :: exp ! (exp ! exp) ! exp ture of normal forms are enforced by the Haskell type sys- tem they can be sure that their algorithm: a) preserves typ- type Hoas = 8exp . CompLam exp ) exp ing and b) outputs normal forms. In this section we use some similar ideas, but our goals Following Atkey et al [6] it is straightforward to convert are slightly different. Our implementation is for call-by- from the HOAS representation to the FOAS representation value computational lambda-calculus with sums. Our im- by defining a suitable instance of the CompLam type class. plementation takes typed HOAS as input and outputs FOAS hoasToExp :: Hoas ! Exp terms in normal form. We use a GADT in conjunction with hoasToExp v = evalGen v 0 a type class in order to explicitly represent Haskell types as terms. Thus we can leverage the Haskell type checker to instance CompLam (Gen Exp) where statically check that the input term is well-typed and that its lam f = do x nextName type matches up with the input type explicitly supplied to e f (return (Var x)) return$ Lam x e the normalisation function. As in the Danvy et al’s imple- v1 ‘app‘ v2 = do e1 v1 mentation, it is necessary to explicitly write the type of the e2 v2 term being normalised somewhere, as the type Haskell will return$ App e1 e2 infer will be polymorphic, whereas our object language is inl v = do e v monomorphic. return$ Inl e The full Haskell source is available at the follow- inr v = do e v ing URL: http://homepages.inf.ed.ac.uk/slindley/ return$ Inr e nbe/nbe-sums.hs. case_ v l r = do e v We begin by defining an algebraic datatype for repre- x1 nextName senting computational lambda calculus terms extended with x2 nextName sums. e1 l (return (Var x1)) e2 r (return (Var x2)) type Var = String return$ Case e x1 e1 x2 e2 let_ v f = do e v data Exp = Var Var x nextName j Lam Var Exp e’ f (return (Var x)) j App Exp Exp return$ Let x e e’ j Inl Exp j Inr Exp Instead of outputting a de Bruijn representation, here we j Case Exp Var Exp Var Exp choose to use a name generation monad, targetting a repre- j Let Var Exp Exp sentation with explicit names in order to fit in with Filinski’s monadic treatment of names. We use the Haskell String type to represent variables. type Gen = State Int Terms are constructed from variables, lambda, application, left injection, right injection, case and let. As Filinski re- nextName :: Gen Var marks, let is redundant; it is included in order to give nicer nextName = normal forms. We chose not to include products in the pre- do { i get; put (i+1); return ("x" ++ show i) } sentation because there is little of interest to say about them and it is straightforward to add them. evalGen :: Gen a ! Int ! a The first-order Exp datatype is rather a verbose way evalGen = evalState of writing syntax and makes no guarantees about binding. The simple types of our source language are given by the We choose to use higher-order abstract syntax as our in- grammar put syntax, which automatically restricts us to closed terms and makes use of Haskell’s built-in binding. Following σ; τ ::= A j B j C j σ ! τ j σ + τ 2 where A; B; C are abstract base types. In Haskell, for + class Monad m ) Residualising m where we write ⊕ (in order to avoid clashing with the built-in +) gamma :: Gen a ! m a which is syntactic sugar for the Either datatype. collect :: m Exp ! Gen Exp bind :: Exp ! m Var data A binds :: Exp ! m (Var ⊕ Var) data B data C Filinski assumes that a residualising monad is layered atop [11] a name generation monad. type a ⊕ b = Either a b The gamma operation is a monad morphism lifting a Following Atkey et al [6], we define a GADT Rep computation of type a in the name generation monad to a of representations of simple types along with a typeclass computation of type a in the residualising monad. Representable a which allows us to smoothly bridge The bind and binds operations respectively introduce the gap between Haskell types and object language type rep- let and case bindings, by storing the bound term and return- resentations. ing its name inside the residualising monad. The collect operation collects all the bindings from data Rep :: ? ! ? where the residualising monad. A :: Rep A B :: Rep B The collect, bind and binds operations must sat- C :: Rep C isfy the following equations. ( ) :: Rep a ! Rep b ! Rep (a ! b) collect (return e) = return e (⊕) :: Rep a ! Rep b ! Rep (a ⊕ b) collect (bind e>>=f) = do x nextName class Representable a where e’ collect (f x) rep :: Rep a return (Let x e e’) collect (binds e>>=f) = instance Representable A where rep = A do x1 nextName instance Representable B where rep = B x2 nextName instance Representable C where rep = C e1 collect (f (Left x1)) e2 collect (f (Right x2)) instance (Representable a, Representable b) ) return (Case e x1 e1 x2 e2) Representable (a ! b) where rep = rep rep These equations give a rather direct correspondence be- tween the syntactic and semantic representations of bind- instance (Representable a, Representable b) ) ings.