Dear TH experts, I have a problem concerning the interaction of quasi quotes and the quotation monad. Assume a code generating function f (... -> Q Exp) which is parameterized by a code generating function g (of type Exp -> Q Exp, or(?) Q Exp -> Q Exp). Expressing the problem in the simplest form, the actual instance for g is (\x -> [| h($x) |]), where h is a toplevel Haskell function working on arbitrary types, and function f instantiates x with an expression which consists just of a single variable (VarE). In order to splice x in the code ($x), the type of x must be (Q Exp). The reason for that, as mentioned in the 2002 paper by Sheard and Peyton Jones "Template Metaprogramming for Haskell", is that the computation of x must be able to access the Q monad. The place inside f where the actual name for the variable x is generated, has already access to the Q monad and the *result* of g is embedded in this monad, no problem. However, I cannot figure out how this monad can be passed as an *argument* to g and conceptually, there is no justification to pass this monad: it is just an offspring version of the one where the lexical scope of g belongs to. The value I want to pass for x is of type Exp. Of course, I could turn this type into (Q Exp) by applying return, but this artificial instance of the Q monad would come from nowhere, not being connected with the regular instance used, e.g., for the fresh name generation. Especially, I have the following questions: * Is there a simple solution to this problem? If so, please tell me and forget about the following questions. * Is the quasi quote mechanism at all appropriate for what I want to do or should one better change to the concrete AST representation? That would be unfortunate because my aim is to develop Template Haskell examples which demonstrate ease of use. * If return is used to turn an expression into monadic form before splicing, is it possible that (a) the consistency of fresh name generation is lost, even if one does the name generation for the spliced expression oneself, (b) something else goes wrong? Many thanks in advance and a Happy New Year -- Christoph Herrmann
On Mon, Jan 02, 2006 at 05:16:36PM +0100, Ch. A. Herrmann wrote:
The value I want to pass for x is of type Exp. Of course, I could turn this type into (Q Exp) by applying return, but this artificial instance of the Q monad would come from nowhere, not being connected with the regular instance used, e.g., for the fresh name generation.
It seems that you are a bit confused with monads. The only monad you have here is the Q type constructor. Applying "return" to your value you'll get a Q-action, which can be one of parts used to construct bigger and bigger Q-actions with >>=. It's a usual way of doing things with monads, don't be afraid. Haskell is designed in such a way, that when you're facing a possibility of undefined behaviour, etc, you see some "danger" signs in your code, like uses of unsafePerformIO (unsafe!!!). In all other cases that typechecker should reject badly behaving programs.
Especially, I have the following questions: * Is there a simple solution to this problem? If so, please tell me and forget about the following questions.
You gave it.
* Is the quasi quote mechanism at all appropriate for what I want to do or should one better change to the concrete AST representation? That
would be unfortunate because my aim is to develop Template Haskell examples which demonstrate ease of use.
As long as it works, it should be appropriate. I used it in this way and it was a lot easier than writing those huge, baroque AST representations. After all that's what quasi-quote is for - to make your life easier!
* If return is used to turn an expression into monadic form before splicing, is it possible that (a) the consistency of fresh name generation is lost, even if one does the name generation for the spliced expression oneself,
No, everything should be fine. Best regards Tomasz -- I am searching for a programmer who is good at least in some of [Haskell, ML, C++, Linux, FreeBSD, math] for work in Warsaw, Poland
Hi Tomasz, Tomasz Zielonka wrote:
On Mon, Jan 02, 2006 at 05:16:36PM +0100, Ch. A. Herrmann wrote:
The value I want to pass for x is of type Exp. Of course, I could turn this type into (Q Exp) by applying return, but this artificial instance of the Q monad would come from nowhere, not being connected with the regular instance used, e.g., for the fresh name generation.
It seems that you are a bit confused with monads. The only monad you have here is the Q type constructor.
maybe you misunderstood me. Take the example of the ST monad. If you are in an ST monadic computation with state, then apply a non-monadic function in which you run a ST monad again, you are faced with two different states. As long as you finished the second monadic computation before you continue the first, this will not be a problem. But if you are forced to insert a monadic value (not a return value) from the second activation into the first activation, you have two different states, and you have to consider how to proceed. If you copy parts of the second activation into the first state, you'll have problem when you leave the second and continue the first activation. Thus, the only serious solution I can figure out at the moment is to ignore the state of the second activation when inserting a value from the second one in the first one. Coming back to the Q monad this would mean: ignore the name generation history from the point where the expression to be spliced was *created* and take the name generation history from the point where the splice is to be *inserted*, which is an older one. Anyway, the names inside the generated splice take the newer information from the lexical scope of the creation. However, I'm worried about that by turning an expression into a monadic form just by using *return* tags this splice with a blank name history and I hope that the quasi quote implementation will never proceed with this history but with the one from the lexical sope of the insertion point.
In all other cases that typechecker should reject badly behaving programs.
Unfortunately, I've had too many bad experiences which make me feel not that optimistic, especially when working with state. Best wishes -- Christoph
On Mon, Jan 02, 2006 at 07:36:03PM +0100, Ch. A. Herrmann wrote:
Hi Tomasz,
Hi!
It seems that you are a bit confused with monads. The only monad you have here is the Q type constructor.
maybe you misunderstood me. Take the example of the ST monad. If you are in an ST monadic computation with state, then apply a non-monadic function in which you run a ST monad again, you are faced with two different states.
Well, yes, but that's because there is runST :: (forall s. ST s a) -> a With Q there is no such function, or at least you are not supposed to use it in normal code.
As long as you finished the second monadic computation before you continue the first, this will not be a problem. But if you are forced to insert a monadic value (not a return value) from the second activation into the first activation, you have two different states, and you have to consider how to proceed. If you copy parts of the second activation into the first state, you'll have problem when you leave the second and continue the first activation. Thus, the only serious solution I can figure out at the moment is to ignore the state of the second activation when inserting a value from the second one in the first one.
The type of runST uses local universal quantification exactly to prevent you from mixing things that belong to different State Threads. All ST references, mutable arrays have a thread tag - that's what the 's' type parameter is for.
Coming back to the Q monad this would mean: ignore the name generation history from the point where the expression to be spliced was *created* and take the name generation history from the point where the splice is to be *inserted*, which is an older one.
When you define var = [| $(liftM VarE (newName "a")) |] there is no name generation history involved (at the place of definition). This definition should be equivalent to var = liftM VarE (newName "a") It is only when you actually run this code, for example in a top-level splice, when the name generation history comes into play. Think of Q as type Q a = NameGenerationState -> (a, NameGenerationState) Such a function can exist without NameGenerationState. NameGenerationState is only neccesary when you want to compute the result of such function, not when you are only using it to build more and more complicated functions.
Anyway, the names inside the generated splice take the newer information from the lexical scope of the creation.
I am not sure, but I think that lexical scopes in TH are implemented indepentendly from name generation and the Q monad. Seems like I have to read the TH papers again.
However, I'm worried about that by turning an expression into a monadic form just by using *return* tags this splice with a blank name history and I hope that the quasi quote implementation will never proceed with this history but with the one from the lexical sope of the insertion point.
Monad laws state that "return" should be a pretty harmless function: (return x) >>= f == f x m >>= return == m
Unfortunately, I've had too many bad experiences which make me feel not that optimistic, especially when working with state.
Could you describe your experiences in more detail? I can see that playing with experimental stuff like Template Haskell or with inherently unsafe features like the FFI can be less safe, but when you exclude those, it's quite difficult to break things by accident, or even on purpose! Best regards Tomasz -- I am searching for a programmer who is good at least in some of [Haskell, ML, C++, Linux, FreeBSD, math] for work in Warsaw, Poland
Hello Ch., Monday, January 02, 2006, 9:36:03 PM, you wrote: CAH> maybe you misunderstood me. Take the example of the ST monad. If you are i feel that it will be clearer if you will compare Q with IO monad -- Best regards, Bulat mailto:bulatz@HotPOP.com
Hi Bulat, Bulat Ziganshin wrote:
Hello Ch.,
Monday, January 02, 2006, 9:36:03 PM, you wrote:
CAH> maybe you misunderstood me. Take the example of the ST monad. If you are
i feel that it will be clearer if you will compare Q with IO monad
the comparison doesn't work with the IO monad because the user runs the Q monad every time when splicing a generated expression. In contrast, the user can't run the IO monad out of a functional non-monadic expression, except when making use of unsafe features. Concerning the ST monad (for simplicity, consider one defined by the user without local type quantification) it is possible, in principle, to join the states of two ST runs overlapping in time, if you have a concept of what you would like to achieve, may depend on the application. Tomasz mentioned an important point: the monad laws for return. As long as they are respected by the quasi quote implementation, a return cannot destroy monadic information in the scope where the splice is inserted, so it is safe to turn an Exp into a (Q Exp) just be using return. How to generate the names inside this Exp is the job of the user, at the point where the spliced expression was created there should be a Q monad available with an appropriate name generation history. As long as the expression to be spliced is taken from the same scope, and this is the case in simple examples, there is no problem because the expression need not be passed as a parameter but can just be cited, even across quotation brackets due to cross-stage persistence. However, I think, with Tomasz remark we should not worry further about that point. -- Christoph
On Tue, Jan 03, 2006 at 04:13:53PM +0100, Ch. A. Herrmann wrote:
the comparison doesn't work with the IO monad because the user runs the Q monad every time when splicing a generated expression.
I am not sure about it. At least it seems that the "name generation state" is passed between splices. Try this: module Gen where import Language.Haskell.TH gen :: Q [Dec] gen = do a <- newName "a" b <- newName "b" expr <- [| (1, 2) |] return [ValD (TupP [VarP a, VarP b]) (NormalB expr) []] ---------------------------------------- module X where import Gen $(gen) $(gen) $(gen) $(gen) $(gen) :browse X shows something like this: a[a2ss] :: Integer a[a2us] :: Integer a[a2vj] :: Integer a[a2wa] :: Integer a[a2x1] :: Integer b[a2su] :: Integer b[a2uu] :: Integer b[a2vl] :: Integer b[a2wc] :: Integer b[a2x3] :: Integer BTW, you can use such a definition g: (\x -> [| h $(return x) |]) now x can be Expr. It's just moving "return" into g. Best regards Tomasz -- I am searching for programmers who are good at least in (Haskell || ML) && (Linux || FreeBSD || math) for work in Warsaw, Poland
Hello Ch., Tuesday, January 03, 2006, 6:13:53 PM, you wrote:
i feel that it will be clearer if you will compare Q with IO monad
CAH> the comparison doesn't work with the IO monad because the user runs the CAH> Q monad every time when splicing a generated expression.
imvho, you don't "feel" the Q monad properly. you cannot splice expression in arbitrary place because there is no runST equivalent for Q monad in more detailed view, all state monads, including ST, IO and Q is a way to DECLARE sequence of some operations having side effects. in ST monad, thos side effects affects state s, which is a parameter of monad. afaik, as you pass this state explicitly between operations, you can turn monadic code in pure functional one in the IO monad, state modified is RealWorld. The only way to get it is from main function. Functions with type "... -> IO a" is the only functions which receives RealWorld as one of its arguments and then returns modified RealWorld. Because of it, IO monad can't be escaped, in other words - non-IO fucntion just don't receive and don't return RealWorld, so it can't modify it in any way and can't execute IO actions, which need this parameter. This ceremony of receiving/returning RealWorld just used to "burn" action to some place in the sequence of IO actions, called by the `main`. and having place in that sequence is the only way to ensure that some action will be really executed. so, RealWorld is just an artifical state, used to sequence actions and link them to `main` Q monad is the way to sequence all name-generation operations. that is a global task, so all actions in Q monad must be "linked" to some top-level splice to be really executed. all top-level splices are executed sequentially like statements in `do` action and name-generator state are passed between these calls, as Tomasz mentioned unless you use runQ (and it's a very bad practice), you cannot escape Q monad, and it's fair - you must inform TH about order in which you want to generate names, and "burning" all your Q actions to some place in the global sequence (i.e. execute them in some top-level splice sequence) is the method of archiving that CAH> In contrast, the user can't run the IO monad out of a functional non-monadic CAH> expression, except when making use of unsafe features. CAH> Concerning the ST monad (for simplicity, consider one defined by the user CAH> without local type quantification) it is possible, in principle, to join CAH> the states of CAH> two ST runs overlapping in time, if you have a concept of what you would CAH> like to CAH> achieve, may depend on the application. CAH> Tomasz mentioned an important point: the monad laws for return. As long as CAH> they are respected by the quasi quote implementation, a return cannot CAH> destroy CAH> monadic information in the scope where the splice is inserted, so it is CAH> safe to CAH> turn an Exp into a (Q Exp) just be using return. How to generate the names CAH> inside this Exp is the job of the user, at the point where the spliced CAH> expression CAH> was created there should be a Q monad available with an appropriate CAH> name generation history. As long as the expression to be spliced is CAH> taken from the same CAH> scope, and this is the case in simple examples, there is no problem CAH> because the expression CAH> need not be passed as a parameter but can just be cited, even across CAH> quotation brackets CAH> due to cross-stage persistence. i feel that we both right :) yes, the Q monad is like ST in that it carries explicit state - number of last generated name. and yes, Q is like IO in what it's "one-for-all" and cannot be escaped, i.e. executed independent of main sequence :) -- Best regards, Bulat mailto:bulatz@HotPOP.com
participants (3)
-
Bulat Ziganshin -
Ch. A. Herrmann -
Tomasz Zielonka