8 References and Acknowledgments
Eli Barzliay’s blog post, +
8 References and Acknowledgments
Eli Barzilay’s blog post, Writing ‘syntax-case’ Macros, helped me understand many key details and concepts, and inspired me to use a "bottom-up" approach.
Eli wrote another blog post, diff --git a/Transform_.html b/Transform_.html index c30970d..68c9f42 100644 --- a/Transform_.html +++ b/Transform_.html @@ -26,7 +26,7 @@ information about lexical scoping (which you don’t need to worry about now, but will turn out to be important later.)
There are a variety of functions available to access a syntax object. Let’s define a piece of syntax:
> (define stx #'(if x (list "true") #f)) > stx #<syntax:11:0 (if x (list "true") #f)>
Now let’s use functions that access the syntax object. The source information functions are:
(syntax-source stx) is returning 'eval, -only becaue of how I’m generating this documentation, using an +only because of how I’m generating this documentation, using an evaluator to run code snippets in Scribble. Normally this would be something like "my-file.rkt".
> (syntax-source stx) 'eval
> (syntax-line stx) 11
> (syntax-column stx) 0
More interesting is the syntax "stuff" itself. syntax->datum converts it completely into an S-expression:
> (syntax->datum stx) '(if x (list "true") #f)
Whereas syntax-e only goes "one level down". It may return a diff --git a/all.html b/all.html index 088180b..e0fc1d5 100644 --- a/all.html +++ b/all.html @@ -1,6 +1,6 @@
Fear of Macros

Contents:
1 Preface
I learned Racket after 25 years of mostly using C and C++.
Some psychic whiplash resulted.
"All the parentheses" was actually not a big deal. Instead, the first +
Contents:
1 Preface
I learned Racket after 25 years of mostly using C and C++.
Some psychic whiplash resulted.
"All the parentheses" was actually not a big deal. Instead, the first mind warp was functional programming. Before long I wrapped my brain around it, and went on to become comfortable and effective with many other aspects and features of Racket.
But two final frontiers remained: Macros and continuations.
I found that simple macros were easy and understandable, plus there @@ -69,7 +69,7 @@ information about lexical scoping (which you don’t need to worry about now, but will turn out to be important later.)
There are a variety of functions available to access a syntax object. Let’s define a piece of syntax:
> (define stx #'(if x (list "true") #f)) > stx #<syntax:11:0 (if x (list "true") #f)>
Now let’s use functions that access the syntax object. The source information functions are:
(syntax-source stx) is returning 'eval, -only becaue of how I’m generating this documentation, using an +only because of how I’m generating this documentation, using an evaluator to run code snippets in Scribble. Normally this would be something like "my-file.rkt".
> (syntax-source stx) 'eval
> (syntax-line stx) 11
> (syntax-column stx) 0
More interesting is the syntax "stuff" itself. syntax->datum converts it completely into an S-expression:
> (syntax->datum stx) '(if x (list "true") #f)
Whereas syntax-e only goes "one level down". It may return a @@ -228,7 +228,7 @@ message:
...: ellipses format-id that lets us format identifier names more succinctly than what we did above:
> (require (for-syntax racket/syntax))
> (define-syntax (hyphen-define/ok3 stx) (syntax-case stx () [(_ a b (args ...) body0 body ...) (with-syntax ([name (format-id stx "~a-~a" #'a #'b)]) #'(define (name args ...) body0 body ...))])) > (hyphen-define/ok3 bar baz () #t) > (bar-baz) #t
Using format-id is convenient as it handles the tedium of converting from syntax to symbol datum to string ... and all the way -back.
4.1.4 Another example
Finally, here’s a variation that accepts an arbitary number of name +back.
4.1.4 Another example
Finally, here’s a variation that accepts an arbitrary number of name parts to be joined with hyphens:
> (require (for-syntax racket/string racket/syntax))
> (define-syntax (hyphen-define* stx) (syntax-case stx () [(_ (names ...) (args ...) body0 body ...) (let* ([names/sym (map syntax-e (syntax->list #'(names ...)))] [names/str (map symbol->string names/sym)] [name/str (string-join names/str "-")] [name/sym (string->symbol name/str)]) (with-syntax ([name (datum->syntax stx name/sym)]) #`(define (name args ...) body0 body ...)))])) > (hyphen-define* (foo bar baz) (v) (* 2 v)) > (foo-bar-baz 50) 100
To review:
You can’t use a pattern variable outside of a template. But you can use syntax or #’ on a pattern variable to make an ad hoc, "fun size" template.
If you want to munge pattern variables for use in the @@ -258,7 +258,7 @@ these are represented by nested
; This macro: > (require (for-syntax racket/syntax)) > (define-syntax (hash.refs stx) (syntax-case stx () ; If the optional ‘default' is missing, assume it's #f. [(_ chain) #'(hash.refs chain #f)] [(_ chain default) (let ([xs (map (lambda (x) (datum->syntax stx (string->symbol x))) (regexp-split #rx"\\." (symbol->string (syntax->datum #'chain))))]) (with-syntax ([h (car xs)] [ks (cdr xs)]) #'(hash-refs h 'ks default)))])) ; Gives us "sugar" to say this: > (hash.refs js.a.b.c) "value"
; Try finding a key that doesn't exist: > (hash.refs js.blah) #f
; Try finding a key that doesn't exist, specifying the default: > (hash.refs js.blah 'did-not-exist) 'did-not-exist
It works!
We’ve started to appreciate that our macros should give helpful messages when used in error. Let’s try to do that here.
> (require (for-syntax racket/syntax)) > (define-syntax (hash.refs stx) (syntax-case stx () ; Check for no args at all [(_) (raise-syntax-error #f "Expected (hash.key0[.key1 ...] [default])" stx #'chain)] [(_ chain) #'(hash.refs chain #f)] [(_ chain default) ; Check that chain is a symbol, not e.g. a number or string (unless (symbol? (syntax-e #'chain)) (raise-syntax-error #f "Expected (hash.key0[.key1 ...] [default])" stx #'chain)) (let ([xs (map (lambda (x) (datum->syntax stx (string->symbol x))) (regexp-split #rx"\\." (symbol->string (syntax->datum #'chain))))]) ; Check that we have at least hash.key (unless (and (>= (length xs) 2) (not (eq? (syntax-e (cadr xs)) '||))) (raise-syntax-error #f "Expected hash.key" stx #'chain)) (with-syntax ([h (car xs)] [ks (cdr xs)]) #'(hash-refs h 'ks default)))])) ; See if we catch each of the misuses > (hash.refs) eval:96:0: hash.refs: Expected (hash.key0[.key1 ...]
[default])
at: chain
in: (hash.refs)
> (hash.refs 0) eval:98:0: hash.refs: Expected (hash.key0[.key1 ...]
[default])
at: 0
in: (hash.refs 0 #f)
> (hash.refs js) eval:99:0: hash.refs: Expected hash.key
at: js
in: (hash.refs js #f)
> (hash.refs js.) eval:100:0: hash.refs: Expected hash.key
at: js.
in: (hash.refs js. #f)
Not too bad. Of course, the version with error-checking is quite a bit longer. Error-checking code generally tends to obscure the logic, and -does here. Fortuantely we’ll soon see how syntax-parse can +does here. Fortunately we’ll soon see how syntax-parse can help mitigate that, in much the same way as contracts in normal Racket or types in Typed Racket.
Maybe we’re not convinced that writing (hash.refs js.a.b.c) is really clearer than (hash-refs js '(a b c)). Maybe we @@ -326,7 +326,7 @@ appreciate what
But for now I’ll focus on improving the previous parts.
8 References and Acknowledgments
Eli Barzliay’s blog post, +this.
But for now I’ll focus on improving the previous parts.
8 References and Acknowledgments
Eli Barzilay’s blog post, Writing ‘syntax-case’ Macros, helped me understand many key details and concepts, and inspired me to use a "bottom-up" approach.
Eli wrote another blog post, diff --git a/index.html b/index.html index 39abc4d..7215111 100644 --- a/index.html +++ b/index.html @@ -1,3 +1,3 @@
Fear of Macros On this page:Fear of Macros Fear of Macros
-
Copyright (c) 2012-2014 by Greg Hendershott. All rights reserved.Last updated 2014-06-25T12:36:23Feedback and corrections are welcome here.Contents:
Contents:
...: ellipses format-id that lets us format identifier names more succinctly than what we did above:
> (require (for-syntax racket/syntax))
> (define-syntax (hyphen-define/ok3 stx) (syntax-case stx () [(_ a b (args ...) body0 body ...) (with-syntax ([name (format-id stx "~a-~a" #'a #'b)]) #'(define (name args ...) body0 body ...))])) > (hyphen-define/ok3 bar baz () #t) > (bar-baz) #t
Using format-id is convenient as it handles the tedium of converting from syntax to symbol datum to string ... and all the way -back.
4.1.4 Another example
Finally, here’s a variation that accepts an arbitary number of name +back.
4.1.4 Another example
Finally, here’s a variation that accepts an arbitrary number of name parts to be joined with hyphens:
> (require (for-syntax racket/string racket/syntax))
> (define-syntax (hyphen-define* stx) (syntax-case stx () [(_ (names ...) (args ...) body0 body ...) (let* ([names/sym (map syntax-e (syntax->list #'(names ...)))] [names/str (map symbol->string names/sym)] [name/str (string-join names/str "-")] [name/sym (string->symbol name/str)]) (with-syntax ([name (datum->syntax stx name/sym)]) #`(define (name args ...) body0 body ...)))])) > (hyphen-define* (foo bar baz) (v) (* 2 v)) > (foo-bar-baz 50) 100
To review:
You can’t use a pattern variable outside of a template. But you can use syntax or #’ on a pattern variable to make an ad hoc, "fun size" template.
If you want to munge pattern variables for use in the @@ -113,7 +113,7 @@ these are represented by nested
; This macro: > (require (for-syntax racket/syntax)) > (define-syntax (hash.refs stx) (syntax-case stx () ; If the optional ‘default' is missing, assume it's #f. [(_ chain) #'(hash.refs chain #f)] [(_ chain default) (let ([xs (map (lambda (x) (datum->syntax stx (string->symbol x))) (regexp-split #rx"\\." (symbol->string (syntax->datum #'chain))))]) (with-syntax ([h (car xs)] [ks (cdr xs)]) #'(hash-refs h 'ks default)))])) ; Gives us "sugar" to say this: > (hash.refs js.a.b.c) "value"
; Try finding a key that doesn't exist: > (hash.refs js.blah) #f
; Try finding a key that doesn't exist, specifying the default: > (hash.refs js.blah 'did-not-exist) 'did-not-exist
It works!
We’ve started to appreciate that our macros should give helpful messages when used in error. Let’s try to do that here.
> (require (for-syntax racket/syntax)) > (define-syntax (hash.refs stx) (syntax-case stx () ; Check for no args at all [(_) (raise-syntax-error #f "Expected (hash.key0[.key1 ...] [default])" stx #'chain)] [(_ chain) #'(hash.refs chain #f)] [(_ chain default) ; Check that chain is a symbol, not e.g. a number or string (unless (symbol? (syntax-e #'chain)) (raise-syntax-error #f "Expected (hash.key0[.key1 ...] [default])" stx #'chain)) (let ([xs (map (lambda (x) (datum->syntax stx (string->symbol x))) (regexp-split #rx"\\." (symbol->string (syntax->datum #'chain))))]) ; Check that we have at least hash.key (unless (and (>= (length xs) 2) (not (eq? (syntax-e (cadr xs)) '||))) (raise-syntax-error #f "Expected hash.key" stx #'chain)) (with-syntax ([h (car xs)] [ks (cdr xs)]) #'(hash-refs h 'ks default)))])) ; See if we catch each of the misuses > (hash.refs) eval:96:0: hash.refs: Expected (hash.key0[.key1 ...]
[default])
at: chain
in: (hash.refs)
> (hash.refs 0) eval:98:0: hash.refs: Expected (hash.key0[.key1 ...]
[default])
at: 0
in: (hash.refs 0 #f)
> (hash.refs js) eval:99:0: hash.refs: Expected hash.key
at: js
in: (hash.refs js #f)
> (hash.refs js.) eval:100:0: hash.refs: Expected hash.key
at: js.
in: (hash.refs js. #f)
Not too bad. Of course, the version with error-checking is quite a bit longer. Error-checking code generally tends to obscure the logic, and -does here. Fortuantely we’ll soon see how syntax-parse can +does here. Fortunately we’ll soon see how syntax-parse can help mitigate that, in much the same way as contracts in normal Racket or types in Typed Racket.
Maybe we’re not convinced that writing (hash.refs js.a.b.c) is really clearer than (hash-refs js '(a b c)). Maybe we