Shared modification inside lambdas

I'm developing an embedded DSL in Haskell (for exact real arithmetic work, but that's irrelevant right now.) Among other things, the language contains functions and application, as well as various terms. In one iteration, the abstract syntax looked kinda like this (in spirit, don't worry about the actually available terms...) data Expr = Var String | Const Int | Plus Expr Expr | Quantified Range String Expr | Lambda String Expr | App Expr Expr Here, lambda-terms just have the name of their bound variable and the RHS. Quantified terms are much like specialized lambdas; one note is that you *will* have to evaluate the RHS of the quantified term with many values for the argument. It's obvious how to write an evaluator on these--just keep an environment, etc. All well and good, but it seems to me that if I'm embedding the DSl, shouldn't I be able to use the host language's facilities--for example, function abstractions and applications--directly? Well, I tried this, and it seems it works OK, like so: data Expr = Var String | Const Int | Plus Expr Expr | Quantified Range (Int -> Expr) I replaced Lambda terms and applications with Haskell-level functions. Note that my quantifiers still need internal functions, but I replaced them in the same fashion. However, I have a problem; one thing I often have to do with expressions is refine them, where refine has type: refine :: Expr -> Expr and transforms expressions into more interesting ones via certain, mostly simple rules. The problem comes with functions, either just plain lambdas or quantified ones. The rule for refining functions boils down to just refining (recursively) the RHS; in the old version, that was easy! refine (Lambda var rhs) = Lambda var (refine rhs) I only refine while evaluating terms of type Expr, so top level functions aren't important--by the time I'm evaluating them, I've applied them to arguments--but quantified terms are a problem. I could write something like: refine (Quantified range pred) = Quantified range pred' where pred' = \c -> refine (pred c) But the problem is that this refines the term, again, every time I apply an argument to pred': I know I'm going to apply arguments many times to that new pred, and I want to refine it /once/: refinement is argument-agnostic, it only rearranges the structure of the AST, so in theory it'd be nice if I could refine that structure, just duplicating references to the appropriate free variable where I would have duplicated refences to the appropriate (Var String) before. Am I sore out of luck here? Is there a reasonable way I can implement my internal functions via Haskell functions, but apply argument-agnostic transformations to the RHS in a shared fashion? Or, is there some optimization in GHC that means I don't need to worry about this? Thanks, AHH

Andrew Hunter
All well and good, but it seems to me that if I'm embedding the DSl, shouldn't I be able to use the host language's facilities--for example, function abstractions and applications--directly?
Indeed. Using binding in the host language to represent binding in the embedded language is called higher-order abstract syntax (HOAS).
Well, I tried this, and it seems it works OK, like so:
data Expr = Var String | Const Int | Plus Expr Expr | Quantified Range (Int -> Expr)
(Do you still need or even want "Var String"?)
... I could write something like:
refine (Quantified range pred) = Quantified range pred' where pred' = \c -> refine (pred c)
But the problem is that this refines the term, again, every time I apply an argument to pred' ...
The paper by Jacques Carette, Oleg Kiselyov, and me http://www.cs.rutgers.edu/~ccshan/tagless/jfp.pdf (revised version to appear in JFP) shows how to perform partial evaluation (which is an optimization, like your refinement) using HOAS. However, it's a bit tricky, in a language like Haskell (98) without so-called metaprogramming or staging facilities at the term level, to make the optimizations happen only once (rather than every time the embedded abstraction is invoked). It can be done! Let me point you to some code that we only mention in passing in that paper, which performs type-checking using HOAS. The type-checking happens only once; then the type-checked term can be interpreted many times. http://okmij.org/ftp/tagless-final/ http://okmij.org/ftp/tagless-final/IncopeTypecheck.hs Hope this helps. -- Edit this signature at http://www.digitas.harvard.edu/cgi-bin/ken/sig 2008-11-25 Elimination of Violence Against Women http://unifem.org/vaw/ 1948-12-10 Universal Declaration of Human Rights http://everyhumanhasrights.org
participants (2)
-
Andrew Hunter
-
Chung-chieh Shan