Difficulties with tagless - create "primitives" or compose them

Hi list, there is one thing about the "Finally tagless" EDSL approach that really confuses me: (Well more than one actually but this one more so) When to decide to introduce a term as a primitive in the syntax class and when to define it from primitives already defined. For example this one here: type Arr exp a b = exp a -> exp b class EDSL exp where lam :: (exp a -> exp b) -> exp (Arr exp a b) app :: exp (Arr exp a b) -> exp a -> exp b int :: Int -> exp Int -- Integer literal add :: exp Int -> exp Int -> exp Int sub :: exp Int -> exp Int -> exp Int mul :: exp Int -> exp Int -> exp Int Let's take "mul" here, defined as a "primitive", in other words defined in the EDSL class. Technically, with lam, app and add already defined, I could have defined "mul" outside the EDSL class, just built from the 3 primitive operators. Of course doing so then does not give me the possibility to choose alternative evaluation strategies for "mul" itself, only for lam, app and add. So what is a good measure for deciding when to define a term primitive or not? Günther

Hi Gunther, The "finally tagless" approach can be viewed as one instance of the typecase pattern, which myself and others have investigated the past: http://ropas.snu.ac.kr/~bruno/papers/Typecase.pdf Your problem is related to a problem that is found when designing generic programming libraries. Namely the existence of what we call "ad-hoc" cases. These are cases that most of the times have a default, but sometimes we would like to override that default. This is essentially the source of your confusion. What is the best thing todo: shall I provide a default by using composition by creating a definition for "mul" outside the class; or shall I create a primitive. There is a trade-off between the two. Fortunatelly it is possible to avoid such trade-off. This is one of the things that is described in the EMGM paper, which uses the same variation of the typecase pattern that is used in the "finally tagless" approach: http://ropas.snu.ac.kr/~bruno/papers/ExtensibleGM.pdf Now, for your particular example, this amounts to the following: type Arr exp a b = exp a -> exp b class EDSL exp where lam :: (exp a -> exp b) -> exp (Arr exp a b) app :: exp (Arr exp a b) -> exp a -> exp b int :: Int -> exp Int -- Integer literal add :: exp Int -> exp Int -> exp Int sub :: exp Int -> exp Int -> exp Int class EDSL exp => EDSLMul exp where mul :: exp Int -> exp Int -> exp Int mul e1 e2 = -- a default definition in terms of the primitives in EDSL By creating a subclass and defining "mul" as a default method you get the advantages of the primitive approach ( you can define your own interpretations) with the advantages of the compositional approach (you can modularly add as many as you want without having to touch EDSL). There is a little extra work that you need to do though, because now you'll need to say: instance EDSLMul MyInterpretation to get an instance for mul at a particular interpretation. You can actually avoid this, if you use overlapping instances: instance EDSLMul a then, only when you you really want to override an interpretation, you will need an instance. More generally this approach tells you how you can modularize your EDSLs. Or, put another way, if offers a solution for the expression problem: http://www.daimi.au.dk/~madst/tool/papers/expression.txt Hope this helps! Cheers, Bruno Oliveira On Jun 13, 2010, at 10:58 AM, Günther Schmidt wrote:
Hi list,
there is one thing about the "Finally tagless" EDSL approach that really confuses me: (Well more than one actually but this one more so)
When to decide to introduce a term as a primitive in the syntax class and when to define it from primitives already defined.
For example this one here:
type Arr exp a b = exp a -> exp b
class EDSL exp where lam :: (exp a -> exp b) -> exp (Arr exp a b) app :: exp (Arr exp a b) -> exp a -> exp b
int :: Int -> exp Int -- Integer literal add :: exp Int -> exp Int -> exp Int sub :: exp Int -> exp Int -> exp Int mul :: exp Int -> exp Int -> exp Int
Let's take "mul" here, defined as a "primitive", in other words defined in the EDSL class.
Technically, with lam, app and add already defined, I could have defined "mul" outside the EDSL class, just built from the 3 primitive operators.
Of course doing so then does not give me the possibility to choose alternative evaluation strategies for "mul" itself, only for lam, app and add.
So what is a good measure for deciding when to define a term primitive or not?
Günther
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

Excellent answer! Splitting the Symantics class into pieces is one of the techniques that we didn't need for solving the original problem (tagless partial evaluation without resorting to fancy types) that set us on this track. Which is too bad, because it would have made a nice addition. The 'typecase pattern', on the other hand, was one of the explicit techniques we did use. One of these days, Oleg, Ken and I need to write a follow-up on this, as the code contains a number of extra techniques (in the advanced partial evaluation parts) which were not explained in our paper. And, of course, Oleg's added a number of refinements to the general technique [see his web site]. I must admit that I find it amusing that most people seem to use finally-tagless for writing interpreters, while we created it for partial evaluation and program transformation! [It also allows you to do certain kinds of program analyses, but one needs additional techniques to make that comfortable - another thing to write up properly]. Jacques

Dear Jacques, I have recently found something new that might also prove to be useful for EDSLs. http://blog.sigfpe.com/2009/05/three-projections-of-doctor-futamura.html Dan's blog post doesn't give any code or implementation but in a way it tackles the same problem, and since you also mention partial evaluation and transformation you might also find this interesting. Then again this might be an old hat to you :) Best regards Günther

Günther Schmidt wrote:
I have recently found something new that might also prove to be useful for EDSLs. http://blog.sigfpe.com/2009/05/three-projections-of-doctor-futamura.html
Dan's blog post doesn't give any code or implementation but in a way it tackles the same problem, and since you also mention partial evaluation and transformation you might also find this interesting.
Actually, we tried to do the 2nd Futamura projection in tagless-final style -- and could not. This paper http://www.daimi.au.dk/~ko/papers/pldi142_rendel1.pdf [1] documents why we were not able to: such self-embeddings need "infinite type towers", which neither Haskell not O'Caml have. We did have a work-around using both a object-language-level 'let' and a meta-language-level 'let', but it was unsatisfactory and, in the end, we cut that whole section out of the JFP paper. It would be interesting to see if using the techniques of Atkey-Lindley-Yallop [2] http://homepages.inf.ed.ac.uk/slindley/papers/unembedding.pdf would make this easier. I have not had a chance to try. Jacques [1] @inproceedings{1542509, author = {Rendel, Tillmann and Ostermann, Klaus and Hofer, Christian}, title = {Typed self-representation}, booktitle = {PLDI '09: Proceedings of the 2009 ACM SIGPLAN conference on Programming language design and implementation}, year = {2009}, isbn = {978-1-60558-392-1}, pages = {293--303}, location = {Dublin, Ireland}, doi = {http://doi.acm.org/10.1145/1542476.1542509}, publisher = {ACM}, address = {New York, NY, USA}, } [2] @inproceedings{1596644, author = {Atkey, Robert and Lindley, Sam and Yallop, Jeremy}, title = {Unembedding domain-specific languages}, booktitle = {Haskell '09: Proceedings of the 2nd ACM SIGPLAN symposium on Haskell}, year = {2009}, isbn = {978-1-60558-508-6}, pages = {37--48}, location = {Edinburgh, Scotland}, doi = {http://doi.acm.org/10.1145/1596638.1596644}, publisher = {ACM}, address = {New York, NY, USA}, }
participants (3)
-
Bruno Oliveira
-
Günther Schmidt
-
Jacques Carette