% Learning Racket #2: Macros, Macros and a Bit of Modules
series: top: “Learning Racket” series toplink: /#racket prev: /learning-racket-1 next: /learning-racket-3
After spending two days doing nothing but sleeping, waking up, watching the visitor counter, reading comments on HN and falling asleep again... Okay, here we go.
As I mentioned before, I'm going to switch to using internal
wherever possible instead of
let-forms. However, my plan is a bit more
complicated; first I need to learn some things.
How to make an alias for
The thing is, “define” is too long. Why not use
def instead? And the name
doesn't seem to be taken already.
Will the same way that I used to define an alias for
> (define-syntax-rule (def id body) (define id body)) > (def name "Artyom") > name "Artyom"
I have a feeling, however, that it won't work if body consists of multiple expressions. Let's check.
(def (vector-swap vec i j) (def t (vector-ref vec i)) (vector-set! vec i (vector-ref vec j)) (vector-set! vec j t))
And indeed I get an error message :(
def: use does not match pattern: (def id body) in: (def (vector-swap vec i j) (def t (vector-ref vec i)) (vector-set! vec i (vector-ref vec j)) (vector-set! vec j t))
define-syntax-rule is, as the docs
a special form, and probably allows more interesting patterns than
id. Let's skip some (okay, more than a dozen) chapters and read
TRG: 16.1. Pattern-Based Macros
A pattern-based macro replaces any code that matches a pattern to an expansion that uses parts of the original syntax that match parts of the pattern.
I wonder what would happen if I define two macros in terms of each other. Will it work? In what order are macros applied? (Oh darn, now I'm curious about how Lisp interpreters work in general. Don't think about it, don't think about it...)
The simplest way to create a macro is to use
define-syntax-rule:(define-syntax-rule pattern template)
As a running example, consider the
swapmacro, which swaps the values stored in two variables. It can be implemented using
define-syntax-ruleas follows:(define-syntax-rule (swap x y) (let ([tmp x]) (set! x y) (set! y tmp)))
So that's how to swap two variables in Racket. Call-by-value, right.
> (let ([x 'x] [y 'y]) (swap x y) (list x y)) '(y x)
Cool. What about misuse-resistance?
> (let ([x 'x] [y 'y]) (swap (add1 x) (sub1 y)) (list x y)) set!: not an identifier in: (add1 x)
Not cool – this error is as misleading as Haskell's infamous
*** Exception: Prelude.head: empty list which pops whenever
somewhere somebody tries to extract the first element of
an empty list. Can I at least restrict
swap to just single
What patterns am I allowed to use?
Where's the big blinking link saying “10 Amazing Racket Macro Patterns You Didn't Know About”?
What do docs say about
Equivalent to(define-syntax id (syntax-rules () [(id . pattern) template]))
Ni-ice. What about
Equivalent to(lambda (stx) (syntax-case stx (literal-id ...) [(generated-id . pattern) (syntax-protect #'template)] ...))
Further down the rabbit hole.
(syntax-case stx-expr (literal-id ...) clause ...) clause = [pattern result-expr] | [pattern fender-expr result-expr] pattern = _ | id | (pattern ...) | (pattern ...+ . pattern) | (pattern ... pattern ellipsis pattern ...) | (pattern ... pattern ellipsis pattern ... . pattern) | #(pattern ...) | #(pattern ... pattern ellipsis pattern ...) | #&pattern | #s(key-datum pattern ...) | #s(key-datum pattern ... pattern ellipsis pattern ...) | (ellipsis stat-pattern) | const stat-pattern = id | (stat-pattern ...) | (stat-pattern ...+ . stat-pattern) | #(stat-pattern ...) | const ellipsis = ...
[after reading all patterns' descriptions]
None of them seem to match “single identifier” (or “number”, for that matter, or “string”...). In fact, I glanced over this chapter and haven't found anything relevant. But this seems like a problem which should be common enough; maybe it's discussed in the next chapter?
- Syntax Objects
- Macro Transformer Procedures
- Mixing Patterns and Expressions:
- Compile and Run-Time Phases
- General Phase Levels
- Phases and Bindings
- Phases and Modules
- Syntax Taints
Hm, which one should I choose... My gut tells me it's subchapter 3. (By “my gut” I mean, of course, the part of brain which is trained to recognise words it has seen before and ignore everything else.)
...With this definition,
(swap x 2)provides a syntax error originating from
I won't look at how it's don— no, wait, it's actually more or less understandable even at this point; perhaps I should just read this small chapter from the beginning and see how much I can understand.
TRG: 16.2.3. Mixing Patterns and Expressions:
syntax-caseform does not produce a procedure. Instead, it starts with a stx-expr expression that determines the syntax object to match against the patterns. Also, each
syntax-caseclause has a pattern and expr, instead of a pattern and template. Within an expr, the
syntaxform – usually abbreviated with
#'– shifts into template-construction mode; if the expr of a clause starts with
#', then we have something like a
syntax-rulesform:> (syntax->datum (syntax-case #'(+ 1 2) () [(op n1 n2) #'(- n1 n2)])) '(- 1 2)
As I understand it,
#' (which is an alias for
syntax) creates a
representation for expressions which can include patternish stuff like
syntax-case allows matching on such representations and
transforming them; finally,
syntax->datum reverses the
transformation carried out by
syntax->datum do when it encounters a pattern?
> (syntax->datum #'(id ...)) syntax: no pattern variables before ellipsis in template in: ...
Wait... I don't actually know that patterns accepted by
syntax-case are the same ones which
#' wants, right? And
A syntax object combines a simpler Racket value, such as a symbol or pair, with lexical information about bindings, source-location information, syntax properties, and tamper status. In particular, an identifier is represented as a symbol object that combines a symbol with lexical and other information.
Sigh. I'm tempted to delete the above paragraphs and pretend that I understood everything correctly from the first try. Must... resist... temptation...
...Okay, second try. As I understand it,
#' (which is an alias for
syntax) creates a representation for Racket expressions, taking
into consideration scoping – i.e. say,
'map is not related to
in any way, but
#'map is. (For Haskellers here, it seems to be the
same thing as
[| map |].) Then,
syntax-case allows matching on
such representations and transforming them; finally,
reverses the transformation carried out by
We could write the
syntax-rules:(define-syntax (swap stx) (syntax-case stx () [(swap x y) #'(let ([tmp x]) (set! x y) (set! y tmp))]))
I wonder: [gee, am I saying “I wonder” too often...] which of those
swaps is responsible for the actual name I use when calling the
(define-syntax (eyjafjallajökull stx) (syntax-case stx () [(swap a b) #'(let ([t a]) (set! a b) (set! b t))]))
> (let ([x 1] [y 2]) (swap x y) (list x y)) swap: unbound identifier in module in: swap
So it's the first one. (In fact, re-reading the docs for
suggests that I can even replace
(swap a b) with
(_ a b) and
nothing will change.)
() mean in
(syntax-case stx () ...?
The answer, again, is found in the docs. (When reading, keep in mind
that everything that could've been inside of
() is referenced as
An id matches any syntax object when it is not bound to
_and does not have the same binding as any literal-id. The id is further bound as pattern variable for the corresponding fender-expr (if any) and result-expr.
An id that has the same binding as a literal-id matches a syntax object that is an identifier with the same binding in the sense of
Apparently, it allows me to select which identifiers won't be
interpreted as wildcards in
Let me try:
(define-syntax (: stx) (syntax-case stx (+) [(_ l ... + r ...) #'(+ (: l ...) (: r ...))]))
<pattern> ... is supposed to match any number of tokens which match
the pattern (I wonder whether it's left- or right-associative, by the
way). The exact syntax of
... is unclear (for instance, how do I
match any-number-of more complex patterns? And how do I refer to
matched tokens later?), but for now I'm satisfied with this partial
syntax-case: misplaced ellipsis in pattern (follows other ellipsis) in: ...
At first I thought it was just me being stupid, but after some
tinkering around and googling I found
this message on
Racket mailing list, which states that it's impossible to do with
syntax-case and advises to use
Why, why must everything be Inferior By Default? But anyway:
(define-syntax (: stx) (syntax-parse stx #:literals (+) [(_ l ... + r ...) #'(+ (: l ...) (: r ...))]))
#:literals (+) – an optional parameter – is used instead of
a mandatory list parameter.
> (: 1 + 2) :: expected more terms starting with the literal symbol `+' or any term in: (1)
Ah right, I forgot about the base case.
(define-syntax (: stx) (syntax-parse stx #:literals (+) [(_ l ... + r ...) #'(+ (: l ...) (: r ...))] [(_ x) #'x]))
> (: 1 + 2) 3
Great. Now I have to add more operators!
(define-syntax (: stx) (syntax-parse stx #:literals (+ - * / ^) [(_ l ... + r ...) #'(+ (: l ...) (: r ...))] [(_ l ... - r ...) #'(- (: l ...) (: r ...))] [(_ l ... * r ...) #'(* (: l ...) (: r ...))] [(_ l ... / r ...) #'(/ (: l ...) (: r ...))] [(_ l ^ r ...) #'(expt l (: r ...))] [(_ x) #'x]))
(This works because when
/ are processed, we've already
dealt with all
-. Also, the case for
^ is different
because it's right-associative.)
syntax-parse: literal is unbound in phase 0 (phase 0 relative to the enclosing module) in: ^
Hey, what? The only difference between
^ is that the latter
isn't already bound to
expt. Why would it matter if I'm doing the
interpretation myself anyway...
that it matters. The sidenote to
#:literals section says:
syntax-parserequires all literals to have a binding. To match identifiers by their symbolic names, use
~datumpattern form instead.
There's probably some good reason for it, but I've no idea what it could be.
(define-syntax (: stx) (syntax-parse stx #:datum-literals (+ - * / ^) [(_ l ... + r ...) #'(+ (: l ...) (: r ...))] [(_ l ... - r ...) #'(- (: l ...) (: r ...))] [(_ l ... * r ...) #'(* (: l ...) (: r ...))] [(_ l ... / r ...) #'(/ (: l ...) (: r ...))] [(_ l ^ r ...) #'(expt l (: r ...))] [(_ x) #'x]))
> (: 1 + 3 - (+ 1 2 3) * 4 / 5 + 3 * 7 ^ 2 ^ 2 * 5 - 3 / 5 / 8 - 9 / 7) 36012 47/56
It works, it works!
(And, as you can see, it evals everything correctly (this time I checked it in GHCi to make sure) and also isn't limited to just numbers – I can use any Racket expressions as tokens.)
Paste the long expression into code area (not REPL) and reload (it should print the result of its evaluation upon reload).
Press the “Macro Stepper” button in the upper right corner.
Press “Step” repeatedly.
I wanted to add actual evaluation here as well (so that I'd see how
(+ 1 2) gets simplified to
3), but didn't find any functions named
eval-syntax-and-syntaxify-again. I'll put it off
for a while.
Do you still remember where it all started? I wanted to write a
macro and then decided to read one “small chapter”. Ha.
hm, I wonder whether people who try to follow this all will find it very confusing
nah, probably not
there's not much to follow actually
and hopefully they don't expect to learn Racket merely by reading how I was learning it
they'll have to write their own macros and make their own mistakes
because nobody has ever learned a programming language without writing a line of code
I've spent half of this day writing down what I forgot to write down yesterday and fixing typos / minor inaccuracies. So, no learning today.
These posts are split in three-days-sized chunks. However, a) it takes me more than three days to write about three days, and b) learning-and-writing is twice as slow as just learning (but probably also better). If you want to have an estimate of how long it will take you to acquire the same knowledge I have at some point, divide the time it took me to do it by 2–6 (the actual coefficient depends on your intelligence and whether you prefer to ask questions or spend hours googling everything by yourself).
Speaking of questions... I am the type of person who would rather
make a hundred Google queries than ask for help from a human, and I suspect
I'm not the only one who is like that. So, here's an offer: if you have a
question which is related to what I've covered so far, you should submit it
and I'll try to investigate it. You get an answer, and I get more knowledge /
don't have to strain my imagination thinking “hm, what other questions I
could have but didn't?”.
Back to TRG 16.2.3
One advantage of using
syntax-caseis that we can provide better error reporting for
swap. For example, with the
(swap x 2)produces a syntax error in terms of
2is not an identifier. We can refine our
swapto explicitly check the sub-forms:(define-syntax (swap stx) (syntax-case stx () [(swap x y) (if (and (identifier? #'x) (identifier? #'y)) #'(let ([tmp x]) (set! x y) (set! y tmp)) (raise-syntax-error #f "not an identifier" stx (if (identifier? #'x) #'y #'x)))]))
With this definition,
(swap x 2)provides a syntax error originating from
Understood. (Notice how
identifier? is defined not on things (
x) but on
syntax objects (
It still annoys me that this is how I should be writing
swap. However, I have two hopes:
syntax-parseturns out to be more advanced and lets me do what I want.
Syntax pattern language is in some way extensible and I can define my own pattern which only matches identifiers.
syntax-parseand the specification facility, syntax classes, use a common language of syntax patterns, which is described in detail in Syntax Patterns.
Click... As you can see,
syntax-parse's pattern language is a bit
syntax-case's. EH-patterns! A-patterns!
~peek-not! O, wonder! How many goodly
creatures patterns are
...ahem. This works:
(define-syntax (swap stx) (syntax-parse stx [(_ (~var x id) (~var y id)) #'(let ([t x]) (set! x y) (set! y t))]))
> (swap 3 x) swap: expected identifier in: 3
~var x id means that
x must belong to the class of identifiers
(the full list of standard classes is
Moreover, there turns out to be a convenient shortcut:
(define-syntax (swap stx) (syntax-parse stx [(_ x:id y:id) #'(let ([t x]) (set! x y) (set! y t))]))
Moreover, I can define my own conventions for literal names!
#lang racket (require syntax/parse) (require (for-syntax syntax/parse)) (define-conventions xyz-as-ids [x id] [y id] [z id]) (define-syntax (swap stx) (syntax-parse stx #:conventions (xyz-as-ids) [(_ x y) #'(let ([t x]) (set! x y) (set! y t))]))
syntax-parse: expected identifier defined as a conventions in: xyz-as-ids
Oops. What's even more interesting, the example still works:
(define-conventions xyz-as-ids [x id] [y id] [z id]) (syntax-parse #'(a b c 1 2 3) #:conventions (xyz-as-ids) [(x ... n ...) (syntax->datum #'(x ...))])
'(a b c)
Wait... I can remember reading something about having to use
(for-syntax syntax/parse) being a bad sign.
Where it was?.. Google says:
The phase level mismatch is easily remedied by putting the syntax class definition within a
Sigh. That's what I get for jumping back and forth instead of following the tutorial.
#lang racket (require (for-syntax syntax/parse)) (begin-for-syntax (define-conventions xyz-as-ids [x id] [y id] [z id])) (define-syntax (swap stx) (syntax-parse stx #:conventions (xyz-as-ids) [(_ x y) #'(let ([t x]) (set! x y) (set! y t))]))
> (swap 3 x) swap: expected identifier in: 3
Skipping the unfinished rest of chapter 16.1 because, because... fuck it, just because. I'm not in the mood.
(By the by: why doesn't DrRacket indent
This is inconvenient.)
(define-syntax (def stx) (syntax-parse stx [(_ x ...) #'(define x ...)]))
Seems wrong to spend a day hopping around only to write this, but whatever.
Improvement #1: multiple declarations
This is stupid:
(def x 1) (def y 2) (def z 3)
This – isn't:
(def [x 1] [y 2] [z 3])
Okay, hm. To define many things inside of a single expression, I can use
begin with multiple
> (begin (define x 1) (define y 2)) > (list x y) '(1 2)
Ha, the unanswered question from previous day finally came up. How do I lift syntax expressions?
For instance, how can I write a macro which transforms
1 2 3 into
'(3 2 1)? This won't work:
(define-syntax (rev stx) (syntax-parse stx [(_ x ...) #'(reverse (list x ...))]))
The trouble is – as you can check with Macro Stepper – that
is a part of generated code, while I want it to be applied to the
syntax object at compile time.
And, as usual, I've traded 15 seconds of asking a person on
15 minutes of reading the docs.
syntax-e “unpacks” one layer of syntax-ing:
> (syntax-e #'(1 2 (add1 3))) '(#<syntax:6:15 1> #<syntax:6:17 2> #<syntax:6:19 (add1 3)>)
packs everything back:
> (datum->syntax #f (list 1 2 3)) #<syntax (1 2 3)>
It doesn't do anything on objects which are already packed (unlike
> (datum->syntax #f (list 1 #'2 3)) #<syntax (1 2 3)> > #'(list 1 #'2 3) #<syntax:40:4 (list 1 (syntax 2) 3)>
With these two pieces, reversing a list finally becomes an easy task:
(define-syntax (rev stx) (syntax-parse stx [(_ x ...) (define xs (syntax-e #'(x ...))) (define rev-xs (reverse xs)) (datum->syntax stx (cons list rev-xs))])) (rev 1 2 3)
I've just remembered about my favorite learning method: whenever you
feel like “there should be a library function for this”, ditch all
guides and tutorials and read the fucking manual. In this case,
reading the fucking manual turned up that there is a
and it does exactly what I wanted (remember?):
> (require (for-syntax racket/syntax)) > (begin-for-syntax (print (syntax-local-eval #'(+ 1 2)))) 3
Now this should be enough to see how things are evaluated:
(define-syntax (evaluating stx) (syntax-parse stx [(_ x ...) (datum->syntax #f (syntax-local-eval #'(x ...)))]))
...turns out it isn't:
> (evaluating + 1 2) ?: literal data is not allowed; no #%datum syntax transformer is bound in: 3
Hm. Googling the error message turns up a couple of links, but they
aren't helpful. Some guy wrote something about
Maybe it's because I thought that
datum->syntax doesn't need any
context and wrote
#f instead of
stx like I always did before?
(define-syntax (evaluating stx) (syntax-parse stx [(_ x ...) (datum->syntax stx (syntax-local-eval #'(x ...)))]))
> (evaluating + 1 2 3) 6
(Now I wonder how it managed to compute
6 at all.)
I've spent the last hour trying to make my infix-macro show reduction steps properly, and I think now it's not possible to do with just a couple of added lines. Maybe later. G'night!
First, a fun fact: how'd you write a macros which takes many lists, each with two elements, and reverses the order of those elements inside each pair?
(define-syntax (foo stx) (syntax-parse stx [(_ (a b) ...) #'(list (b a) ...)]))
> (foo (3 +) (5 /)) '(3 1/5)
Lesson: I underestimated the power of ellipses. Don't be like me.
def macro which handles multiple declarations!
(define-syntax (def stx) (syntax-parse stx [(_ (v:id ex:expr) ...) #'(begin (define v ex) ...)]))
ex:expr means that
ex is an expression.)
> (def [x 1] [y 2] [z 3]) > (list z y x) '(3 2 1)
Improvement #2: defining functions together with variables
Should be pretty easy. Here's the syntax I want:
(def [(f x y) (/ (+ x y) 2)] [pi 3.141592653589793])
(Interesting, I just found out I know pi exactly to the precision of
And here's the proposed desugaring:
(begin (define f (lambda (x y) (/ (+ x y) 2))) (define pi 3.141592653589793))
Well. First of all, let's define another macro –
def1 – which would
be handling a single definition.
(define-syntax (def1 stx) (syntax-parse stx [(_ v:id ex:expr) #'(define v ex)] [(_ (f:id p:id ...) body ...+) #'(define f (lambda (p ...) body ...))]))
...+ means “many, but at least one”.)
(For whatever reason DrRacket has been barfing at this definition when I tried it with the example, but accepting it after swapping the clauses. Ctrl-R didn't help and I had to restart DrRacket.)
def becomes a bit simpler:
(define-syntax (def stx) (syntax-parse stx [(_ (d ...) ...) #'(begin (def1 d ...) ...)]))
(The reason for using
(_ (d ...) ...) instead of
(_ d ...) is that
I don't want to pass brackets to
Non-improvement: recursive declarations
Originally I wanted to implement them too (and even written a couple
of paragraphs about how I found out that the standard way of producing
undefined in Racket seems to be
(letrec ([x x]) x)), but then it
turned out to be working already. So, just see it in action (and
please don't blame me for stealing an example from the
(def [(is-even? n) (or (zero? n) (is-odd? (sub1 n)))] [(is-odd? n) (and (not (zero? n)) (is-even? (sub1 n)))])
> (list (is-even? 6) (is-odd? 6)) '(#t #f)
Improvement #3: multiple return values
The last enemy that shall be destroyed is
which allows binding multiple return values. Unfortunately, the syntax
let-values uses clashes with syntax for function definitions.
What syntax should I choose? Something like
[_ (x y) (values 1 2)],
perhaps? But then I lose the ability to define
What is not definable?
> (define #:answer 37) define: expected 42 in: #:answer 37
Just kidding, just kidding.
> (define #:answer 37) define: bad syntax in: #:answer
#: also counts as a keyword. So, one more clause...
(define-syntax (def1 stx) (syntax-parse stx [(_ v:id ex:expr) #'(define v ex)] [(_ #: (v:id ...) ex:expr) #'(define-values (v ...) ex)] [(_ (f:id p:id ...) body ...+) #'(define f (lambda (p ...) body ...))]))
And here's how to use it:
> (def [#: (l r) (split-at (range 10) 5)]) > (list l r) '((0 1 2 3 4) (5 6 7 8 9))
Semi-improvement #4: indentation
For some reason I was thinking that indentation information is somehow stored with functions/macros themselves. I was wrong.
def indent correctly, add it to
syntax-parse, add it to
Lambda-like Keywords (I wonder why
it isn't there already).
Making a module
def nicely concludes these three days I've spent learning about
macros. I intend to use it instead of various
let-forms in the
future – so I'd better make a module exporting it.
Google “racket modules”. Since I'm feeling
sleepy lucky, follow
Each Racket module typically resides in its own file. For example, suppose the file
"cake.rkt"contains the following module:#lang racket (provide print-cake) ; draws a cake with n candles (define (print-cake n) (show " ~a " n #\.) (show " .-~a-. " n #\|) (show " | ~a | " n #\space) (show "---~a---" n #\-)) (define (show fmt n ch) (printf fmt (make-string n ch)) (newline))
Okay, I added
(provide def) to my file.
"cake.rkt":#lang racket (require "cake.rkt") (print-cake (random 30))
Not nice. I want
artyom/def or something.
A collection is a hierarchical grouping of installed library modules. A module in a collection is referenced through an unquoted, suffixless path. For example, the following module refers to the
"date.rkt"library that is part of the
"racket"collection:#lang racket (require racket/date) (printf "Today is ~s\n" (date->string (seconds->date (current-seconds))))
An unquoted, suffixless path... Yeah.
You could add a new collection by placing files in the Racket installation or one of the directories reported by
(get-collects-search-dirs). Alternatively, you could add to the list of searched directories by setting the
PLTCOLLECTSenvironment variable. The best option, however, is to add a package.
I'll check what
get-collects-search-dirs returns just for the sake
of it, but
the best option is probably the best one,
> (require setup/dirs) > (get-collects-search-dirs) '(#<path:/home/yom/.racket/6.0/collects> #<path:/usr/share/racket/collects>)
Creating a package does not mean that you have to register with a package server or perform a bundling step that copies your source code into an archive format. Creating a package can simply mean using the package manager to make your libraries locally accessible as a collection from their current source locations.
For example, suppose you have a directory
"/usr/molly/bakery"that contains the
"cake.rkt"module (from the beginning of this section) and other related modules. To make the modules available as a
Use the raco pkg command-line tool:raco pkg install --link /usr/molly/bakery
--linkflag is not actually needed when the provided path includes a directory separator.
Package Manageritem from the
Filemenu. In the
Do What I Meanpanel, click
Browse..., choose the
"/usr/molly/bakery"directory, and click
$ ls code/racket/artyom def.rkt $ raco pkg install code/racket/artyom raco setup: version: 6.0 [3m] raco setup: installation name: 6.0 raco setup: variants: 3m ... raco setup: 3 making: <pkgs>/artyom raco setup: --- creating launchers --- raco setup: --- installing man pages --- raco setup: --- building documentation --- raco setup: --- installing collections --- raco setup: --- post-installing collections ---
Wow, so simple. Check:
#lang racket (require artyom/def) (def [success? #t]) success?
Does it automatically pick up changes? To test this, I'm going to
def.rkt without telling anybody.
(provide deaf) ... (define-syntax (deaf stx) ...
A-a-and... Yes! It behaves just as expected – no source–bytecode
incompatibilities, no cached definitions, nothing, it just picked up
def.rkt right after reload. I didn't even have to
restart DrRacket. Awesome. Gotta use
def everywhere from now on.
I'm afraid that with the level of flexibility Racket allows, I'll never get to writing an actual program – most likely I'll be spending my time improving Racket itself (well, or at least changing it to suit my tastes). It's a dangerous road, because ultimately you can't really improve the tool you're not using. So, as a public promise (?), I want to state that in less than a year I'll write and open-source some medium-sized project in Racket. (And, of course, I'll be documenting the process.)
(An update from 2 years after: the promise has been totally broken.)
I need not say anything about macros at this point, do I.
This part turned out to be both shorter and more tangled than the previous part. Sorry.
gus_massa (from HN)
...has posted a
comment in which
they're nitpicking about
define-syntax-rule; in particular, it seems to
define-syntax-rule works with patterns just fine.
What do the docs say again?
Equivalent to(define-syntax id (syntax-rules () [(id . pattern) template]))
but with syntax errors potentially phrased in terms of pattern.
Pattern, template... Okay, that's right.
Hm, “but”... What's it actual definition? (I wish Racket docs included “source” links like Haskell docs do.)
I'll have to check it with DrRacket. First I need to type some valid
define-syntax-rule (is there a way to get to
the source without jumping thru such hoops, by the by?):
(define-syntax-rule (blah) blah)
Then I can use
Open Defining File from context menu of
define-syntax-rule. It opens
misc.rkt, which contains a rather
lengthy definition consisting of
pattern-failure, which, well, seems
to be used for reporting pattern matching failures, and
(define-syntax define-syntax-rule (lambda (stx) (let-values ([(err) (lambda (what . xs) (apply raise-syntax-error 'define-syntax-rule what stx xs))]) (syntax-case stx () [(dr (name . pattern) template) (identifier? #'name) (syntax/loc stx (define-syntax name (lambda (user-stx) (syntax-case** dr #t user-stx () free-identifier=? #f [(_ . pattern) (syntax-protect (syntax/loc user-stx template))] [_ (pattern-failure user-stx 'pattern)]))))] [(_ (name . ptrn) tmpl) (err "expected an identifier" #'name)] [(_ (name . ptrn)) (err "missing template")] [(_ (name . ptrn) tmpl etc . _) (err "too many forms" #'etc)] [(_ head . _) (err "invalid pattern" #'head)]))))
Hm, okay, what am I protected against?
"expected an identifier"
(define-syntax-rule ('not-an-id x ... p) (p x ...))
define-syntax-rule: expected an identifier in: (quote not-an-id)
(define-syntax ('not-an-id stx) (syntax-case stx () [(_ x ... p) #'(p x ...)]))
> ('not-an-id 1 2 3 +) quote: received value from syntax expander was not syntax received: #<procedure>
Not nice – it didn't even get caught at compile-time.
(define-syntax-rule (foo x ... p))
define-syntax-rule: missing template in: (define-syntax-rule (foo x ... p))
I can guess what
define-syntax will do...
(define-syntax (foo stx) (syntax-case stx ()))
> (foo 1 2 3 +) foo: bad syntax in: (foo 1 2 3 +)
I won't bother checking the rest.
I won't bother using
define-syntax-rule either, tho. If I ever need
to extend a macro defined with it, it'll be more troublesome to switch
syntax-parse than if I used it right from the start. Not to
syntax-parse supports more cool stuff and who doesn't
like cool stuff?
(Oh, and if I ever get tired of typing
define-syntax ... stx ... syntax-case ... stx,
I'll be sure to write a macro to make it easier.)