I have the following code that composes a traditional macro eat which uses a quote ('(eating food)), with a make-rename-transformer macro that is supposed to transform lunch into sandwich. The full code is:
#lang racket
(require (for-syntax syntax/parse))
(define-syntax lunch (make-rename-transformer #'sandwich))
(define-syntax (eat stx)
(syntax-parse stx
[(_ food)
#''(eating food)]))
(eat lunch)
Because lunch is just a rename transfomer for sandwich, I would expect it to evaluate to (eat sandwich), and thus '(eating sandwich), but when I run it, I get:
'(eating lunch)
Which is not what I expected. Is there any way I can modify this so that rename transforms in quotes are followed? (As if I had used the list function rather than quote.)
The problem here is just an order of expansion. Unlike functions which, to a first approximation anyway, evaluate inside out. Macros by nature expand outside in, which is an intrinsic property of macro expansion in languages like Racket.
So, the problem is that the first step of macro expa
#lang racket
(define-syntax lunch (make-rename-transformer #'sandwich))
'(eating lunch)
At this point lunch is no longer an identifier, but just a datum, so the rename transformer won't apply here.
What you need to do is tell the macro expander to expand food before you place it in the quote and turn it into datum. Racket's macro expander contains this functionality, [local-expand], if you combine this with #:when (syntax/parse's version of with-syntax), you can get the macro to evaluate food first.
Putting it all together, your code looks like:
#lang racket
(require (for-syntax syntax/parse))
(define-syntax lunch (make-rename-transformer #'sandwich))
(define-syntax (eat stx)
(syntax-parse stx
[(_ food)
#:with expanded-food (local-expand #'food 'expression #f)
#''(eating expanded-food)]))
(eat lunch)
And when you run this, you get:
'(eating sandwich)
Related
I am using syntax transformers to define macros in Racket. I want to create some helper functions to help me manipulate the syntax. However, the functions I defined outside the syntax transformer are not available inside the syntax transformer. For example, in the following code
(define (my-function x) (+ x 1))
(define-syntax my-macro
(lambda (stx)
(datum->syntax stx (my-function (cadr (syntax->datum stx))))))
I got the error "my-function: reference to an unbound identifier at phase: 1; the transformer environment".
After some searching, I am able to write the following code so that my-function is available inside the syntax transformer.
(begin-for-syntax
(define (my-function x) (+ x 1)))
(provide (for-syntax my-function))
(define-syntax my-macro
(lambda (stx)
(datum->syntax stx (my-function (cadr (syntax->datum stx))))))
But the problem is, my-function is not available outside the syntax transformer this time. Sometimes I want to check those helper functions in ordinary code, so I need to be able to call it from both inside and outside the syntax transformer, just like the function cadr. How can I achieve that?
I know my question has something to do with Racket's syntax model, in particular the concept of "phase level", but I never really understand it. If you could provide some easy-to-follow tutorials explaining it I would even be more grateful.
A common way is to define your function that you want to share across phases in another (sub)module. Then, require it twice.
#lang racket
(module common racket
(provide my-function)
(define (my-function x) (+ x 1)))
(require 'common
(for-syntax 'common))
(define-syntax my-macro
(lambda (stx)
(datum->syntax stx (my-function (cadr (syntax->datum stx))))))
(my-function 1)
(my-macro 123)
I am trying to learn how to write Macros in Racket.
I've gone through the excellent Fear of Macros tutorial and am now trying to experiment with the basic ideas.
So I defined my-if slightly differently from what was shown in the tutorial, and I am getting an error define-values: unbound identifier; in:
#lang racket/base
(define-syntax (my-if stx)
(define-values (_ condition yes no)
(apply values (syntax->list stx)))
(datum->syntax stx `(if ,condition ,yes ,no)))
If I use #lang racket instead of racket/base it works.
Looking up the definition of define-values in the Manual, it seems to be part of racket/base!
define-values provided from racket/base, racket
So why is Racket complaining about this?
Use (require (for-syntax racket/base)) to import racket/base also at phase 1 (compile time). Using #lang racket/base provides define-values only at phase 0 (runtime).
While experimenting with racket's macros, I stumbled into a definition that wasn't at first obvious to me why it was rejected. The code is short and otherwise is probably useless, but is as follows:
#lang racket
(define-syntax (go stx)
(syntax-case stx ()
[(_ id)
#'(module mod racket
(define it id))]
))
(go 'dummy)
The complaint is quote: unbound identifier; also, no #%app syntax transformer...
If I manually inline (define it id) to (define it 'dummy) then it works.
I had a hunch that ' ie. quote of (go 'dummy) that is bound by #lang racket is not recognized as the same binding within the submodule mod even though syntactically it is the same sequence of letters. If I strip 'dummy of all lexical context by round tripping as follows:
(with-syntax ([ok (datum->syntax #f (syntax->datum #'id))])
below the pattern match (_ id) and replace definition of it with (define it ok) then all is good again.
#lang racket
(define-syntax (go stx)
(syntax-case stx ()
[(_ id)
(with-syntax ([ok (datum->syntax #f (syntax->datum #'id))])
#'(module mod racket
(define it ok)))]
))
(go 'dummy)
I presume that my dilemma was caused by the hygiene system. However, is there a more direct solution to convince racket compiler that these identifiers, ie. quote are really the same without this boilerplate?
The expression that you insert for id in:
(module mod racket
(define it id))
is going to be evaluated in the context of the module.
Therefore the syntactic context id id needs to be the same as
the context of the submodule.
You describe one way of removing existing context. Here is another:
#lang racket
(require (for-syntax racket/base))
(define-syntax (go stx)
(syntax-case stx ()
[(_ id)
(with-syntax ([id (syntax->datum #'id)])
#'(module mod racket
(provide it)
(define it id)))]))
(go 42)
(require (submod "." mod))
it
In most macros it is a good thing that context is preserved, so
having to "boiler plate" to remove it seems okay to me.
Of course, if you experience to much boiler plate then write
a macro that inserts the boiler plate for you :-)
My son and I are learning Racket together and are building a very simple text-based adventure for use directly from the REPL. So, for example, the player can type (go 'north) or (take 'apple).
After getting some basic stuff working, my son thought that quoting the noun was a bit of a pain (strangely, the parens don't bother him!), and so we hacked around with macros for a bit and we did get something working but it required an explicit function and a corresponding macro e.g.
(define (do-take item) ...)
(define-syntax (take stx)
(define item (cadr (syntax->datum stx)))
(datum->syntax stx `(do-take ',item)))
I figured we could do better than this, so I read around a bit more and came up with this:
(require (for-syntax racket/syntax))
(define-syntax (define-verb stx)
(syntax-case stx ()
[(_ (verb noun) body-first body-rest ...)
(with-syntax ([verb-fun (format-id stx "do-~a" #'verb)])
#'(begin
(define-syntax-rule (verb noun) (verb-fun 'noun))
(define (verb-fun noun) body-first body-rest ...)))]))
So now, we can write (define-verb (take item) ...) and the player at the REPL can type (take apple).
My question is whether, given what we want to achieve, this is a reasonable approach or whether there a more simple / idiomatic way to achieve the same thing?
In general, the main thing I would recommend doing is using the syntax/parse library. It has more tools for parsing syntax. You can even use forms like define-syntax-parser to make your macro even more concise. Rewriting your code using syntax/parse (dropping that one line because it doesn't seem to be doing anything), your macro would look like this:
#lang racket
(require syntax/parse/define
(for-syntax syntax/parse racket/syntax))
(define-syntax-parser define-verb
[(_ (verb:id noun) body ...+)
(define/syntax-parse verb-fun (format-id stx "do-~a" #'verb))
#'(begin
(define-simple-macro (verb noun) (verb-fun 'noun))
(define (verb-fun noun) body ...))])
This gives you a few nice things above the example you gave:
the :id ensures that verb is a literal identifier, rather than an expression.
the ...+ means you only need to have one body pattern, rather than two.
Using define/syntax-parse means your code does not get more indented than with-syntax. (Although this one is a matter of preference.)
I found myself defining syntax parameters with identical definitions except for their name so I decided to write a macro to make this simpler:
(define-syntax (test-case-parameter stx)
(syntax-parse stx
[(_ parameter:id)
#'(define-syntax-parameter parameter
(lambda (stx)
(raise-syntax-error stx "Can only be used inside test-case.")))]))
(test-case-parameter a)
(test-case-parameter b)
(test-case-parameter c)
However instead of having to repeat the macro name I would like to be able to just write:
(test-case-parameter a b c)
But I don't see how to do this using the normal ellipses syntax, because I would need to wrap everything in a begin which would create a new scope, and I want all of the syntax parameters as if I had written them each of the top level. What's the right way to accomplish this?
The answer is to use begin. begin is weird, because it has different behavior at the top-level than it does in an expression context. At the top-level, it has the kind of splicing behavior you would want for this macro, but in an expression context is has the scoping behavior you're referring to.
So you can define your macro like this:
#lang racket
(require racket/stxparam (for-syntax syntax/parse))
(define-syntax (define-test-case-parameters stx)
(syntax-parse stx
[(_ parameter:id ...)
#'(begin
(define-syntax-parameter parameter
(lambda (stx)
(raise-syntax-error stx "Can only be used inside test-case.")))
...)]))
(define-test-case-parameters a b c)
You can see how the begin top-level splicing works in the Macro Stepper in DrRacket:
Make a new macro that accepts multiple identifiers and let it expand to a sequence of usages of your version that uses a single identifier.
#lang racket
(require (for-syntax syntax/parse)
racket/stxparam)
(define-syntax (test-case-parameter-helper stx)
(syntax-parse stx
[(_test-case-parameter-helper parameter:id)
(syntax/loc stx
(define-syntax-parameter parameter
(lambda (stx)
(raise-syntax-error stx "Can only be used inside test-case."))))]))
(define-syntax (test-case-parameter stx)
(syntax-parse stx
[(_test-case-parameter parameter:id ...)
(syntax/loc stx
(begin
(test-case-parameter-helper parameter)
...))]))
(test-case-parameter a b c)