diff --git a/gh.css b/gh.css index 10a987b..50ccc8b 100644 --- a/gh.css +++ b/gh.css @@ -1,5 +1,9 @@ /* Override some default styles for a hopefully fresher look. */ +.strike { + text-decoration: line-through; +} + /*** racket.css ***/ /* Monospace: */ diff --git a/index.html b/index.html index ea7a41c..15cce7c 100644 --- a/index.html +++ b/index.html @@ -1,8 +1,6 @@ -Fear of Macros
1 Preface
2 The plan of attack
3 Transformers
3.1 What is a syntax transformer?
3.2 What’s the input?
3.3 Actually transforming the input
3.4 Compile time vs. run time
3.5 begin-for-syntax
4 Pattern matching: syntax-case and syntax-rules
4.1 Patterns and templates
4.1.1 "A pattern variable can’t be used outside of a template"
5 Syntax parameters
6 Robust macros: syntax-parse
7 Other questions
7.1 What’s the point of with-syntax?
7.2 What’s the point of begin-for-syntax?
7.3 What’s the point of racket/ splicing?
8 References and Acknowledgments
9 Epilogue

Fear of Macros

- -
Copyright (c) 2012 by Greg Hendershott. All rights reserved. -
Last updated 2012-10-31 11:46:36

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 +Fear of Macros

Fear of Macros

+
Copyright (c) 2012 by Greg Hendershott. All rights reserved.
Last updated 2012-11-01 12:11:35
Feedback and corrections are welcome here.

Contents:

    1 Preface

    2 The plan of attack

    3 Transformers

      3.1 What is a syntax transformer?

      3.2 What’s the input?

      3.3 Actually transforming the input

      3.4 Compile time vs. run time

      3.5 begin-for-syntax

    4 Pattern matching: syntax-case and syntax-rules

      4.1 "A pattern variable can’t be used outside of a template"

      4.2 Making our own struct

      4.3 Using dot notation for nested hash lookups

    5 Syntax parameters

    6 Robust macros: syntax-parse

    7 What’s the point of splicing-let?

    8 References and Acknowledgments

    9 Epilogue

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 @@ -171,9 +169,11 @@ territory. Hopefully, because we started with a basic syntax transformer, and worked up from that, we won’t have that problem. We can appreciate define-syntax-rule as a convenient shorthand, but not be scared of, or confused about, that for which it’s -shorthand.

4.1 Patterns and templates

Most of the materials I found for learning macros, including the +shorthand.

Most of the materials I found for learning macros, including the Racket Guide, do a very good job explaining how patterns -work. I’m not going to regurgitate that here.

Instead, let’s look at some ways we’re likely to get tripped up.

4.1.1 "A pattern variable can’t be used outside of a template"

Let’s say we want to define a function with a hyphenated name, a-b, +and templates work. I’m not going to regurgitate that here.

Sometimes, we need to go a step beyond the pattern and template. Let’s +look at some examples, how we can get confused, and how to get it +working.

4.1 "A pattern variable can’t be used outside of a template"

Let’s say we want to define a function with a hyphenated name, a-b, but we supply the a and b parts separately. The Racket struct macro does something like this: (struct foo (field1 field2)) automatically defines a number of functions whose names are variations @@ -208,7 +208,30 @@ converting from syntax to datum and back again.

To review:

  • I template, with-syntax is your friend.

  • You will need to use syntax or # on the pattern variables to turn them into "fun size" templates.

  • Usually you’ll also need to use syntax->datum to get the interesting value inside.

  • format-id is convenient for formatting identifier -names.

5 Syntax parameters

"Anaphoric if" or "aif" is a popular macro example. Instead of writing:

(let ([tmp (big-long-calculation)])
  (if tmp
      (foo tmp)
      #f))

You could write:

(aif (big-long-calculation)
     (foo it)
     #f)

In other words, when the condition is true, an it identifier +names.

4.2 Making our own struct

In this example we’ll pretend that Racket doesn’t already have a +struct capability. Fortunately, we can define a macro to +provide this feature. To keep things simple, our structure will be +immutable (read-only) and it won’t support inheritance.

Given a structure declaration like:

(our-struct name (field1 field2 ...))

We need to define some procedures.

  • A constructor procedure whose name is the struct name. We’ll +represent structures as a vector. The structure name will be +element zero. The fields will be elements one onward.

  • A predicate, whose name is the struct name with ? +appended.

  • For each field, an accessor procedure to get its value. These +will be named struct-field (the name of the struct, a hyphen, and the +field name).

> (require (for-syntax racket/syntax))
> (define-syntax (our-struct stx)
    (syntax-case stx ()
      [(_ id (fields ...))
       (with-syntax ([pred-id (format-id stx "~a?" #'id)])
         #`(begin
             ; Define a constructor.
             (define (id fields ...)
               (apply vector (cons 'id  (list fields ...))))
             ; Define a predicate.
             (define (pred-id v)
               (and (vector? v)
                    (eq? (vector-ref v 0) 'id)))
             ; Define an accessor for each field.
             #,@(for/list ([x (syntax->list #'(fields ...))]
                           [n (in-naturals 1)])
                  (with-syntax ([acc-id (format-id stx "~a-~a" #'id x)]
                                [ix n])
                    #`(define (acc-id v)
                        (unless (pred-id v)
                          (error 'acc-id "~a is not a ~a struct" v 'id))
                        (vector-ref v ix))))))]))
; Test it out
> (require rackunit)
> (our-struct foo (a b))
> (define s (foo 1 2))
> (check-true (foo? s))
> (check-false (foo? 1))
> (check-equal? (foo-a s) 1)
> (check-equal? (foo-b s) 2)
> (check-exn exn:fail?
             (lambda () (foo-a "furble")))
; The tests passed.
; Next, what if someone tries to declare:
> (our-struct "blah" ("blah" "blah"))

format-id: contract violation

  expected: (or/c string? symbol? identifier? keyword? char?

number?)

  given: #<syntax:71:0 "blah">

The error message is not very helpful. It’s coming from +format-id, which is a private implementation detail of our macro.

You may know that a syntax-case clause can take an +optional "guard" or "fender" expression. Instead of

[pattern template]

It can be:

[pattern guard template]

Let’s add a guard expression to our clause:

> (require (for-syntax racket/syntax))
> (define-syntax (our-struct stx)
    (syntax-case stx ()
      [(_ id (fields ...))
       ; Guard or "fender" expression:
       (for-each (lambda (x)
                   (unless (identifier? x)
                     (raise-syntax-error #f "not an identifier" stx x)))
                 (cons #'id (syntax->list #'(fields ...))))
       (with-syntax ([pred-id (format-id stx "~a?" #'id)])
         #`(begin
             ; Define a constructor.
             (define (id fields ...)
               (apply vector (cons 'id  (list fields ...))))
             ; Define a predicate.
             (define (pred-id v)
               (and (vector? v)
                    (eq? (vector-ref v 0) 'id)))
             ; Define an accessor for each field.
             #,@(for/list ([x (syntax->list #'(fields ...))]
                           [n (in-naturals 1)])
                  (with-syntax ([acc-id (format-id stx "~a-~a" #'id x)]
                                [ix n])
                    #`(define (acc-id v)
                        (unless (pred-id v)
                          (error 'acc-id "~a is not a ~a struct" v 'id))
                        (vector-ref v ix))))))]))
; Now the same misuse gives a better error message:
> (our-struct "blah" ("blah" "blah"))

eval:74:0: our-struct: not an identifier

  at: "blah"

  in: (our-struct "blah" ("blah" "blah"))

Later, we’ll see how syntax-parse makes it even easier to +check usage and provide helpful messages about mistakes.

4.3 Using dot notation for nested hash lookups

The previous two examples used a macro to define functions whose names +were made by joining identifiers provided to the macro. This example +does the opposite: The identifier given to the macro is split into +pieces.

If you write programs for web services you deal with JSON, which is +represented in Racket by a jsexpr?. JSON often has +dictionaries that contain other dictionaries. In a jsexpr? +these are represented by nested hasheq tables.

JavaScript you can use dot notation:

foo = js.a.b.c;

In Racket it’s not so convenient:

; Nested hasheqs typical of a jsexpr:
> (define js (hasheq 'a (hasheq 'b (hasheq 'c "value"))))
; Typical annoying code to get something:
> (hash-ref (hash-ref (hash-ref js 'a) 'b) 'c)

"value"

We can write a helper function to make this a bit cleaner:

; This helper function:
> (define/contract (hash-refs h ks [def #f])
    ((hash? (listof any/c)) (any/c) . ->* . any)
    (with-handlers ([exn:fail? (const (cond [(procedure? def) (def)]
                                            [else def]))])
      (for/fold ([h h])
        ([k (in-list ks)])
        (hash-ref h k))))
; Lets us say:
> (hash-refs js '(a b c))

"value"

That’s not bad. Can we go even further and use a dot notation somewhat +like JavaScript?

; This macro:
> (require (for-syntax racket/syntax))
> (define-syntax (hash.refs stx)
    (syntax-case stx ()
      [(_)
       (raise-syntax-error #f "Expected (hash.key0[.key1 ...] [default])"
                           stx #'chain)]
      [(_ chain)
       #'(hash.refs chain #f)]
      [(_ chain default)
       (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))))])
         (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)))]))
; Gives us "sugar" to say this:
> (hash.refs js.a.b.c)

"value"

It works!

We’ve started to appreciate that our macros should give helpful +messages when used in error. We tried to do that here. Let’s +deliberately elicit various errors. Are the messages helpful?

> (hash.refs)

eval:80:0: hash.refs: Expected (hash.key0[.key1 ...]

[default])

  at: chain

  in: (hash.refs)

> (hash.refs 0)

eval:83:0: hash.refs: Expected (hash.key0[.key1 ...]

[default])

  at: 0

  in: (hash.refs 0 #f)

> (hash.refs js)

eval:84:0: hash.refs: Expected hash.key

  at: js

  in: (hash.refs js #f)

> (hash.refs js.)

eval:85:0: hash.refs: Expected hash.key

  at: js.

  in: (hash.refs js. #f)

Not too bad.

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 +won’t actually use this approach. But the Racket macro system makes it +a possible choice.

5 Syntax parameters

"Anaphoric if" or "aif" is a popular macro example. Instead of writing:

(let ([tmp (big-long-calculation)])
  (if tmp
      (foo tmp)
      #f))

You could write:

(aif (big-long-calculation)
     (foo it)
     #f)

In other words, when the condition is true, an it identifier is automatically created and set to the value of the condition. This should be easy:

> (define-syntax-rule (aif condition true-expr false-expr)
    (let ([it condition])
      (if it
          true-expr
          false-expr)))
> (aif #t (displayln it) (void))

it: undefined;

 cannot reference an identifier before its definition

  in module: 'program

Wait, what? it is undefined?

It turns out that all along we have been protected from making a certain kind of mistake in our macros. The mistake is if our new @@ -230,18 +253,15 @@ default. Only inside of our aif will it have a meani value:

> (require racket/stxparam)
> (define-syntax-parameter it
    (lambda (stx)
      (raise-syntax-error (syntax-e stx) "can only be used inside aif")))
> (define-syntax-rule (aif condition true-expr false-expr)
    (let ([tmp condition])
      (if tmp
          (syntax-parameterize ([it (make-rename-transformer #'tmp)])
            true-expr)
          false-expr)))
> (aif 10 (displayln it) (void))

10

> (aif #f (displayln it) (void))

Inside the syntax-parameterize, it acts as an alias for tmp. The alias behavior is created by make-rename-transformer.

If we try to use it outside of an aif form, and -it isn’t otherwise defined, we get an error like we want:

> (displayln it)

it: can only be used inside aif

But we can still define it as a normal variable:

> (define it 10)
> it

10

6 Robust macros: syntax-parse

TO-DO. -TO-DO. -TO-DO.

7 Other questions

Hopefully I will answer these in the course of writing the other -sections. But just in case, here’s a running list:

7.1 What’s the point of with-syntax?

Done.

7.2 What’s the point of begin-for-syntax?

Done.

7.3 What’s the point of racket/splicing?

I stared at racket/splicing for the longest time, not -understanding exactly how it works, or why I’d want to use it. As with -other aspects of Racket macros, step number one was to de-mythologize -it. This:

> (require racket/splicing)
> (splicing-let ([x 0])
    (define (get-x)
      x))
; get-x is visible out here:
> (get-x)

0

; but x is not:
> x

x: undefined;

 cannot reference an identifier before its definition

  in module: 'program

is shorthand for this:

> (define get-y
    (let ([y 0])
      (lambda ()
        y)))
; get-y is visible out here:
> (get-y)

0

; but y is not:
> y

y: undefined;

 cannot reference an identifier before its definition

  in module: 'program

This is the classic Lisp/Scheme/Racket idiom sometimes called "let +it isn’t otherwise defined, we get an error like we want:

> (displayln it)

it: can only be used inside aif

But we can still define it as a normal variable:

> (define it 10)
> it

10

6 Robust macros: syntax-parse

TO-DO.

TO-DO.

TO-DO.

7 What’s the point of splicing-let?

I stared at racket/splicing for the longest time. What does +it do? Why would I use it? Why is it in the Macros section of the +reference?

Step one, cut a hole in the box +de-mythologize it. For example, using splicing-let like this:

> (require racket/splicing)
> (splicing-let ([x 0])
    (define (get-x)
      x))
; get-x is visible out here:
> (get-x)

0

; but x is not:
> x

x: undefined;

 cannot reference an identifier before its definition

  in module: 'program

is equivalent to:

> (define get-y
    (let ([y 0])
      (lambda ()
        y)))
; get-y is visible out here:
> (get-y)

0

; but y is not:
> y

y: undefined;

 cannot reference an identifier before its definition

  in module: 'program

This is the classic Lisp/Scheme/Racket idiom sometimes called "let over lambda". A koan -about closures and objects. A closure hides y, which can’t -be accessed directly, only via get-y.

So why would we care about the splicing forms? They can be more -concise, especially when there are multiple body forms:

> (require racket/splicing)
> (splicing-let ([x 0])
    (define (inc)
      (set! x (+ x 1)))
    (define (dec)
      (set! x (- x 1)))
    (define (get)
      x))

The splicing variation is more convenient than the usual way:

> (define-values (inc dec get)
    (let ([x 0])
      (values (lambda ()  ; inc
                (set! x (+ 1 x)))
              (lambda ()  ; dec
                (set! x (- 1 x)))
              (lambda ()  ; get
                x))))

When there are many body forms—and you are generating them in a +about closures and objects. A closure hides y, which can +only be accessed via get-y.

So why would we care about the splicing forms? They can be more +concise, especially when there are multiple body forms:

> (require racket/splicing)
> (splicing-let ([x 0])
    (define (inc)
      (set! x (+ x 1)))
    (define (dec)
      (set! x (- x 1)))
    (define (get)
      x))

The splicing variation is more convenient than the usual way:

> (define-values (inc dec get)
    (let ([x 0])
      (values (lambda ()  ; inc
                (set! x (+ 1 x)))
              (lambda ()  ; dec
                (set! x (- 1 x)))
              (lambda ()  ; get
                x))))

When there are many body forms—and you are generating them in a macro—the splicing variations can be much easier.

8 References and Acknowledgments

Eli Barzliay’s blog post, Writing ‘syntax-case’ Macros, helped me understand many key details and diff --git a/main.rkt b/main.rkt index 8afc6a5..0a8d62a 100644 --- a/main.rkt +++ b/main.rkt @@ -7,7 +7,8 @@ (for-syntax racket/base) (for-label racket) (for-label racket/stxparam) - (for-label syntax/parse)) + (for-label syntax/parse) + (for-label racket/splicing)) @(define evaluator (parameterize ([sandbox-output 'string] [sandbox-error-output 'string]) @@ -16,14 +17,18 @@ @(define-syntax-rule (i body ...) (interaction #:eval evaluator body ...)) -@image["fear-of-macros.jpg"] @title[#:version ""]{Fear of Macros} -@author[@hyperlink["https://github.com/greghendershott/fear-of-macros/issues" +@author[@hyperlink["http://www.greghendershott.com" "Greg Hendershott"]] -@smaller{Copyright (c) 2012 by Greg Hendershott. All rights reserved.} +@image["fear-of-macros.jpg"] +@para[@smaller{Copyright (c) 2012 by Greg Hendershott. All rights reserved.}] @para[@smaller["Last updated " (parameterize ([date-display-format 'iso-8601]) (date->string (current-date) #t))]] +@para{Feedback and corrections are @hyperlink["https://github.com/greghendershott/fear-of-macros/issues" "welcome here"].} + +Contents: + @table-of-contents{} @; ---------------------------------------------------------------------------- @@ -109,6 +114,7 @@ similar for macro. There is. One of the more-recent Racket macro enhancements is @racket[syntax-parse]. +@; ---------------------------------------------------------------------------- @; ---------------------------------------------------------------------------- @section{Transformers} @@ -217,6 +223,7 @@ When Racket expands our program, it sees the occurrence of calls our function with the old syntax, and we return the new syntax, which is used to evaluate and run our program. +@; ---------------------------------------------------------------------------- @subsection{What's the input?} @@ -298,6 +305,9 @@ When we want to transform syntax, we'll generally take the pieces we were given, maybe rearrange their order, perhaps change some of the pieces, and often introduce brand-new pieces. + +@; ---------------------------------------------------------------------------- + @subsection{Actually transforming the input} Let's write a transformer function that reverses the syntax it was @@ -342,6 +352,7 @@ compiler, and @italic{that} syntax is evaluated: (values "i" "am" "backwards") ] +@; ---------------------------------------------------------------------------- @subsection{Compile time vs. run time} @@ -509,6 +520,8 @@ So let's try that: (our-if-using-match-v2 #t "true" "false") ] +@; ---------------------------------------------------------------------------- + @subsection{@racket[begin-for-syntax]} We used @racket[for-syntax] to @racket[require] the @@ -577,6 +590,7 @@ them available at compile time.} } ] +@; ---------------------------------------------------------------------------- @; ---------------------------------------------------------------------------- @section{Pattern matching: syntax-case and syntax-rules} @@ -647,15 +661,17 @@ can appreciate @racket[define-syntax-rule] as a convenient shorthand, but not be scared of, or confused about, that for which it's shorthand. -@subsection{Patterns and templates} - Most of the materials I found for learning macros, including the Racket @italic{Guide}, do a very good job explaining how patterns -work. I'm not going to regurgitate that here. +and templates work. I'm not going to regurgitate that here. -Instead, let's look at some ways we're likely to get tripped up. +Sometimes, we need to go a step beyond the pattern and template. Let's +look at some examples, how we can get confused, and how to get it +working. -@subsubsection{"A pattern variable can't be used outside of a template"} +@; ---------------------------------------------------------------------------- + +@subsection{"A pattern variable can't be used outside of a template"} Let's say we want to define a function with a hyphenated name, a-b, but we supply the a and b parts separately. The Racket @racket[struct] @@ -826,6 +842,234 @@ names.} @; ---------------------------------------------------------------------------- +@subsection{Making our own @racket[struct]} + +In this example we'll pretend that Racket doesn't already have a +@racket[struct] capability. Fortunately, we can define a macro to +provide this feature. To keep things simple, our structure will be +immutable (read-only) and it won't support inheritance. + +Given a structure declaration like: + +@racketblock[ +(our-struct name (field1 field2 ...)) +] + +We need to define some procedures. + +@itemize[ + +@item{A constructor procedure whose name is the struct name. We'll +represent structures as a @racket[vector]. The structure name will be +element zero. The fields will be elements one onward.} + +@item{A predicate, whose name is the struct name with @tt{?} +appended.} + +@item{For each field, an accessor procedure to get its value. These +will be named struct-field (the name of the struct, a hyphen, and the +field name).} + +] + + +@#reader scribble/comment-reader +(i +(require (for-syntax racket/syntax)) +(define-syntax (our-struct stx) + (syntax-case stx () + [(_ id (fields ...)) + (with-syntax ([pred-id (format-id stx "~a?" #'id)]) + #`(begin + ;; Define a constructor. + (define (id fields ...) + (apply vector (cons (quote id) (list fields ...)))) + ;; Define a predicate. + (define (pred-id v) + (and (vector? v) + (eq? (vector-ref v 0) 'id))) + ;; Define an accessor for each field. + #,@(for/list ([x (syntax->list #'(fields ...))] + [n (in-naturals 1)]) + (with-syntax ([acc-id (format-id stx "~a-~a" #'id x)] + [ix n]) + #`(define (acc-id v) + (unless (pred-id v) + (error 'acc-id "~a is not a ~a struct" v 'id)) + (vector-ref v ix))))))])) + +;; Test it out +(require rackunit) +(our-struct foo (a b)) +(define s (foo 1 2)) +(check-true (foo? s)) +(check-false (foo? 1)) +(check-equal? (foo-a s) 1) +(check-equal? (foo-b s) 2) +(check-exn exn:fail? + (lambda () (foo-a "furble"))) + +;; The tests passed. +;; Next, what if someone tries to declare: +(our-struct "blah" ("blah" "blah")) +) + +The error message is not very helpful. It's coming from +@racket[format-id], which is a private implementation detail of our macro. + +You may know that a @racket[syntax-case] clause can take an +optional "guard" or "fender" expression. Instead of + +@racketblock[ +[pattern template] +] + +It can be: + +@racketblock[ +[pattern guard template] +] + +Let's add a guard expression to our clause: + +@#reader scribble/comment-reader +(i +(require (for-syntax racket/syntax)) +(define-syntax (our-struct stx) + (syntax-case stx () + [(_ id (fields ...)) + ;; Guard or "fender" expression: + (for-each (lambda (x) + (unless (identifier? x) + (raise-syntax-error #f "not an identifier" stx x))) + (cons #'id (syntax->list #'(fields ...)))) + (with-syntax ([pred-id (format-id stx "~a?" #'id)]) + #`(begin + ;; Define a constructor. + (define (id fields ...) + (apply vector (cons (quote id) (list fields ...)))) + ;; Define a predicate. + (define (pred-id v) + (and (vector? v) + (eq? (vector-ref v 0) 'id))) + ;; Define an accessor for each field. + #,@(for/list ([x (syntax->list #'(fields ...))] + [n (in-naturals 1)]) + (with-syntax ([acc-id (format-id stx "~a-~a" #'id x)] + [ix n]) + #`(define (acc-id v) + (unless (pred-id v) + (error 'acc-id "~a is not a ~a struct" v 'id)) + (vector-ref v ix))))))])) + +;; Now the same misuse gives a better error message: +(our-struct "blah" ("blah" "blah")) +) + +Later, we'll see how @racket[syntax-parse] makes it even easier to +check usage and provide helpful messages about mistakes. + + +@subsection{Using dot notation for nested hash lookups} + +The previous two examples used a macro to define functions whose names +were made by joining identifiers provided to the macro. This example +does the opposite: The identifier given to the macro is split into +pieces. + +If you write programs for web services you deal with JSON, which is +represented in Racket by a @racket[jsexpr?]. JSON often has +dictionaries that contain other dictionaries. In a @racket[jsexpr?] +these are represented by nested @racket[hasheq] tables. + +JavaScript you can use dot notation: + +@codeblock{ +foo = js.a.b.c; +} + +In Racket it's not so convenient: + +@#reader scribble/comment-reader +(i +; Nested hasheqs typical of a jsexpr: +(define js (hasheq 'a (hasheq 'b (hasheq 'c "value")))) +; Typical annoying code to get something: +(hash-ref (hash-ref (hash-ref js 'a) 'b) 'c) +) + +We can write a helper function to make this a bit cleaner: + +@#reader scribble/comment-reader +(i +;; This helper function: +(define/contract (hash-refs h ks [def #f]) + ((hash? (listof any/c)) (any/c) . ->* . any) + (with-handlers ([exn:fail? (const (cond [(procedure? def) (def)] + [else def]))]) + (for/fold ([h h]) + ([k (in-list ks)]) + (hash-ref h k)))) + +;; Lets us say: +(hash-refs js '(a b c)) +) + +That's not bad. Can we go even further and use a dot notation somewhat +like JavaScript? + +@#reader scribble/comment-reader +(i +;; This macro: +(require (for-syntax racket/syntax)) +(define-syntax (hash.refs stx) + (syntax-case stx () + [(_) + (raise-syntax-error #f "Expected (hash.key0[.key1 ...] [default])" + stx #'chain)] + [(_ chain) + #'(hash.refs chain #f)] + [(_ chain default) + (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))))]) + (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)))])) +;; Gives us "sugar" to say this: +(hash.refs js.a.b.c) +) + +It works! + +We've started to appreciate that our macros should give helpful +messages when used in error. We tried to do that here. Let's +deliberately elicit various errors. Are the messages helpful? + +@i[ +(hash.refs) +(hash.refs 0) +(hash.refs js) +(hash.refs js.) +] + +Not too bad. + +Maybe we're not convinced that writing @racket[(hash.refs js.a.b.c)] +is really clearer than @racket[(hash-refs js '(a b c))]. Maybe we +won't actually use this approach. But the Racket macro system makes it +a possible choice. + +@; ---------------------------------------------------------------------------- +@; ---------------------------------------------------------------------------- + @section{Syntax parameters} "Anaphoric if" or "aif" is a popular macro example. Instead of writing: @@ -941,35 +1185,28 @@ it ] +@; ---------------------------------------------------------------------------- @; ---------------------------------------------------------------------------- @section{Robust macros: syntax-parse} TO-DO. + TO-DO. + TO-DO. +@; ---------------------------------------------------------------------------- @; ---------------------------------------------------------------------------- -@section{Other questions} +@section{What's the point of @racket[splicing-let]?} -Hopefully I will answer these in the course of writing the other -sections. But just in case, here's a running list: +I stared at @racket[racket/splicing] for the longest time. What does +it do? Why would I use it? Why is it in the Macros section of the +reference? -@subsection{What's the point of @racket[with-syntax]?} - -Done. - -@subsection{What's the point of @racket[begin-for-syntax]?} - -Done. - -@subsection{What's the point of @racket[racket/splicing]?} - -I stared at @racket[racket/splicing] for the longest time, not -understanding exactly how it works, or why I'd want to use it. As with -other aspects of Racket macros, step number one was to de-mythologize -it. This: +Step one, @elem[#:style "strike"]{cut a hole in the box} +de-mythologize it. For example, using @racket[splicing-let] like this: @#reader scribble/comment-reader (i @@ -983,7 +1220,7 @@ it. This: x ) -is shorthand for this: +is equivalent to: @#reader scribble/comment-reader (i @@ -998,10 +1235,10 @@ y ) This is the classic Lisp/Scheme/Racket idiom sometimes called "let -over lambda". @margin-note*{A +over lambda". @margin-note*{A @hyperlink["http://people.csail.mit.edu/gregs/ll1-discuss-archive-html/msg03277.html" "koan"] -about closures and objects.} A closure hides @racket[y], which can't -be accessed directly, only via @racket[get-y]. +about closures and objects.} A closure hides @racket[y], which can +only be accessed via @racket[get-y]. So why would we care about the splicing forms? They can be more concise, especially when there are multiple body forms: @@ -1034,6 +1271,7 @@ The splicing variation is more convenient than the usual way: When there are many body forms---and you are generating them in a macro---the splicing variations can be much easier. +@; ---------------------------------------------------------------------------- @; ---------------------------------------------------------------------------- @section{References and Acknowledgments} @@ -1080,6 +1318,7 @@ projected between the lines. My point is, the Racket documentation is very good. The @italic{Guide} provides helpful examples and tutorials. The @italic{Reference} is very clear and precise. +@; ---------------------------------------------------------------------------- @; ---------------------------------------------------------------------------- @section{Epilogue} @@ -1097,7 +1336,6 @@ later translated by D.T. Suzuki in his @italic{Essays in Zen Buddhism}.} } - Translated into Racket: @racketblock[ @@ -1110,4 +1348,5 @@ Translated into Racket: (lambda () (and (eq? 'mountains 'mountains) (eq? 'rivers 'rivers)))) + ]