diff --git a/Epilogue.html b/Epilogue.html index b247dee..8d6c70d 100644 --- a/Epilogue.html +++ b/Epilogue.html @@ -1,9 +1,9 @@ -9 Epilogue

9 Epilogue

"Before I had studied Chan (Zen) for thirty years, I saw mountains as +9 Epilogue

9 Epilogue

"Before I had studied Chan (Zen) for thirty years, I saw mountains as mountains, and rivers as rivers. When I arrived at a more intimate knowledge, I came to the point where I saw that mountains are not mountains, and rivers are not rivers. But now that I have got its very substance I am at rest. For it’s just that I see mountains once again as mountains, and rivers once again as rivers"

–Buddhist saying originally formulated by Qingyuan Weixin, later translated by D.T. Suzuki in his Essays in Zen -Buddhism.

Translated into Racket:

(dynamic-wind (lambda ()
                (and (eq? 'mountains 'mountains)
                     (eq? 'rivers 'rivers)))
              (lambda ()
                (not (and (eq? 'mountains 'mountains)
                          (eq? 'rivers 'rivers))))
              (lambda ()
                (and (eq? 'mountains 'mountains)
                     (eq? 'rivers 'rivers))))
 
\ No newline at end of file +Buddhism.

Translated into Racket:

(dynamic-wind (lambda ()
                (and (eq? 'mountains 'mountains)
                     (eq? 'rivers 'rivers)))
              (lambda ()
                (not (and (eq? 'mountains 'mountains)
                          (eq? 'rivers 'rivers))))
              (lambda ()
                (and (eq? 'mountains 'mountains)
                     (eq? 'rivers 'rivers))))
 
\ No newline at end of file diff --git a/Our_plan_of_attack.html b/Our_plan_of_attack.html index 4b3f347..576d3fd 100644 --- a/Our_plan_of_attack.html +++ b/Our_plan_of_attack.html @@ -1,5 +1,5 @@ -2 Our plan of attack

2 Our plan of attack

The macro system you will mostly want to use for production-quality +2 Our plan of attack

2 Our plan of attack

The macro system you will mostly want to use for production-quality macros is called syntax-parse. And don’t worry, we’ll get to that soon.

But if we start there, you’re likely to feel overwhelmed by concepts and terminology, and get very confused. I did.

1. Instead let’s start with the basics: A syntax object and a function @@ -19,4 +19,4 @@ they’re used in error. Normal Racket functions optionally can have contracts and types. These catch usage mistakes and provide clear, useful error messages. It would be great if there were something similar for macro. There is. One of the more-recent Racket macro -enhancements is syntax-parse.

 
\ No newline at end of file +enhancements is syntax-parse.

 
\ No newline at end of file diff --git a/Preface.html b/Preface.html index b17932a..1a8d8ca 100644 --- a/Preface.html +++ b/Preface.html @@ -1,5 +1,5 @@ -1 Preface

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 +1 Preface

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 @@ -21,4 +21,4 @@ problems or annoyances. I learn more quickly and deeply when I discover the answer to a question I already have, or find the solution to a problem whose pain I already feel. Therefore I’ll give you the questions and problems first, so that you can better appreciate and -understand the answers and solutions.

 
\ No newline at end of file +understand the answers and solutions.

 
\ No newline at end of file diff --git a/References_and_Acknowledgments.html b/References_and_Acknowledgments.html index fca391e..ddac5b0 100644 --- a/References_and_Acknowledgments.html +++ b/References_and_Acknowledgments.html @@ -1,5 +1,5 @@ -8 References and Acknowledgments

8 References and Acknowledgments

Eli Barzilay’s blog post, +8 References and Acknowledgments

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, @@ -25,4 +25,4 @@ improved since I last read it. Of course, it was the same; I’d changed. It’s interesting how much of what we already know is projected between the lines. My point is, the Racket documentation is very good. The Guide provides helpful examples and -tutorials. The Reference is very clear and precise.

 
\ No newline at end of file +tutorials. The Reference is very clear and precise.

 
\ No newline at end of file diff --git a/Robust_macros__syntax-parse.html b/Robust_macros__syntax-parse.html index 99244d6..12870b3 100644 --- a/Robust_macros__syntax-parse.html +++ b/Robust_macros__syntax-parse.html @@ -1,11 +1,11 @@ -7 Robust macros: syntax-parse
On this page:
7.1 Error-handling strategies for functions
7.2 Error-handling strategies for macros
7.3 Using syntax-parse

7 Robust macros: syntax-parse

Functions can be used in error. So can macros.

7.1 Error-handling strategies for functions

With plain old functions, we have several choices how to handle +7 Robust macros: syntax-parse

7 Robust macros: syntax-parse

Functions can be used in error. So can macros.

7.1 Error-handling strategies for functions

With plain old functions, we have several choices how to handle misuse.

1. Don’t check at all.

> (define (misuse s)
    (string-append s " snazzy suffix"))
; User of the function:
> (misuse 0)

string-append: contract violation

  expected: string?

  given: 0

  argument position: 1st

  other arguments...:

   " snazzy suffix"

; I guess I goofed, but what is this "string-append" of which you
; speak??

The problem is that the resulting error message will be confusing. Our user thinks they’re calling misuse, but is getting an error message from string-append. In this simple example they could probably guess what’s happening, but in most cases they won’t.

2. Write some error handling code.

> (define (misuse s)
    (unless (string? s)
      (error 'misuse "expected a string, but got ~a" s))
    (string-append s " snazzy suffix"))
; User of the function:
> (misuse 0)

misuse: expected a string, but got 0

; I goofed, and understand why! It's a shame the writer of the
; function had to work so hard to tell me.

Unfortunately the error code tends to overwhelm and/or obscure our function definition. Also, the error message is good but not -great. Improving it would require even more error code.

3. Use a contract.

> (define/contract (misuse s)
    (string? . -> . string?)
    (string-append s " snazzy suffix"))
; User of the function:
> (misuse 0)

misuse: contract violation

  expected: string?

  given: 0

  in: the 1st argument of

      (-> string? string?)

  contract from: (function misuse)

  blaming: program

  at: eval:131.0

; I goofed, and understand why! I'm happier, and I hear the writer of
; the function is happier, too.

This is the best of both worlds.

The contract is a simple and concise. Even better, it’s +great. Improving it would require even more error code.

3. Use a contract.

> (define/contract (misuse s)
    (string? . -> . string?)
    (string-append s " snazzy suffix"))
; User of the function:
> (misuse 0)

misuse: contract violation

  expected: string?

  given: 0

  in: the 1st argument of

      (-> string? string?)

  contract from: (function misuse)

  blaming: program

   (assuming the contract is correct)

  at: eval:131.0

; I goofed, and understand why! I'm happier, and I hear the writer of
; the function is happier, too.

This is the best of both worlds.

The contract is a simple and concise. Even better, it’s declarative. We say what we want to happen, not how.

On the other hand the user of our function gets a very detailed error message. Plus, the message is in a standard, familiar format.

4. Use Typed Racket.

> (: misuse (String -> String))
> (define (misuse s)
    (string-append s " snazzy suffix"))
> (misuse 0)

eval:3:0: Type Checker: type mismatch

  expected: String

  given: Zero

  in: 0

Even better, Typed Racket can catch usage mistakes up-front at compile time.

7.2 Error-handling strategies for macros

For macros, we have similar choices.

1. Ignore the possibility of misuse. This choice is even worse for @@ -30,4 +30,4 @@ appreciate what

But for now I’ll focus on improving the previous parts.

 
\ No newline at end of file +this.

But for now I’ll focus on improving the previous parts.

 
\ No newline at end of file diff --git a/Syntax_parameters.html b/Syntax_parameters.html index da13755..321d958 100644 --- a/Syntax_parameters.html +++ b/Syntax_parameters.html @@ -1,5 +1,5 @@ -5 Syntax parameters

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 +5 Syntax parameters

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 @@ -22,4 +22,4 @@ value:

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 in local -definition contexts like:

> (let ([it 10])
    it)

10

or:

> (define (foo)
    (define it 10)
    it)
> (foo)

10

For a deeper look, see Keeping it Clean with Syntax Parameters.

 
\ No newline at end of file +definition contexts like:

> (let ([it 10])
    it)

10

or:

> (define (foo)
    (define it 10)
    it)
> (foo)

10

For a deeper look, see Keeping it Clean with Syntax Parameters.

 
\ No newline at end of file diff --git a/Transform_.html b/Transform_.html index 68c9f42..1e51ed8 100644 --- a/Transform_.html +++ b/Transform_.html @@ -1,5 +1,5 @@ -3 Transform!
On this page:
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

3 Transform!

  YOU ARE INSIDE A ROOM.

  THERE ARE KEYS ON THE GROUND.

  THERE IS A SHINY BRASS LAMP NEARBY.

  

  IF YOU GO THE WRONG WAY, YOU WILL BECOME

  HOPELESSLY LOST AND CONFUSED.

  

  > pick up the keys

  

  YOU HAVE A SYNTAX TRANSFORMER

3.1 What is a syntax transformer?

A syntax transformer is not one of the トランスフォーマ +3 Transform!

3 Transform!

  YOU ARE INSIDE A ROOM.

  THERE ARE KEYS ON THE GROUND.

  THERE IS A SHINY BRASS LAMP NEARBY.

  

  IF YOU GO THE WRONG WAY, YOU WILL BECOME

  HOPELESSLY LOST AND CONFUSED.

  

  > pick up the keys

  

  YOU HAVE A SYNTAX TRANSFORMER

3.1 What is a syntax transformer?

A syntax transformer is not one of the トランスフォーマ transformers.

Instead, it is simply a function. The function takes syntax and returns syntax. It transforms syntax.

Here’s a transformer function that ignores its input syntax, and always outputs syntax for a string literal:

> (define-syntax foo
    (lambda (stx)
      (syntax "I am foo")))

Using it:

> (foo)

"I am foo"

When we use define-syntax, we’re making a transformer @@ -101,4 +101,4 @@ automatically. If we need other modules, we have to require them, and do so for compile time using for-syntax.

  • Similarly, if we want to define helper functions in the same file/module as the macros that use them, we need to wrap the definitions inside a begin-for-syntax form. Doing so makes -them available at compile time.

  •  
    \ No newline at end of file +them available at compile time.

     
    \ No newline at end of file diff --git a/What_s_the_point_of_splicing-let_.html b/What_s_the_point_of_splicing-let_.html index c226742..1b813d3 100644 --- a/What_s_the_point_of_splicing-let_.html +++ b/What_s_the_point_of_splicing-let_.html @@ -1,5 +1,5 @@ -6 What's the point of splicing-let?

    6 What’s the point of splicing-let?

    I stared at racket/splicing for the longest time. What does +6 What's the point of splicing-let?

    6 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 @@ -8,4 +8,4 @@ over lambda". 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 we’re generating them in a -macro—the splicing variations can be much easier.

     
    \ No newline at end of file +macro—the splicing variations can be much easier.

     
    \ No newline at end of file diff --git a/all.html b/all.html index e0fc1d5..aa27ae2 100644 --- a/all.html +++ b/all.html @@ -1,6 +1,6 @@ Fear of Macros
    Fear of Macros
    1 Preface
    2 Our plan of attack
    3 Transform!
    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 Pattern variable vs. template—fight!
    4.1.1 with-syntax
    4.1.2 with-syntax*
    4.1.3 format-id
    4.1.4 Another example
    4.2 Making our own struct
    4.3 Using dot notation for nested hash lookups
    5 Syntax parameters
    6 What’s the point of splicing-let?
    7 Robust macros:   syntax-parse
    7.1 Error-handling strategies for functions
    7.2 Error-handling strategies for macros
    7.3 Using syntax-parse
    8 References and Acknowledgments
    9 Epilogue

    Fear of Macros

    -
    Copyright (c) 2012-2014 by Greg Hendershott. All rights reserved.
    Last updated 2014-07-11T08:56:11
    Feedback and corrections are welcome here.

    Contents:

        1 Preface

        2 Our plan of attack

        3 Transform!

          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 Pattern variable vs. template—fight!

            4.1.1 with-syntax

            4.1.2 with-syntax*

            4.1.3 format-id

            4.1.4 Another example

          4.2 Making our own struct

          4.3 Using dot notation for nested hash lookups

        5 Syntax parameters

        6 What’s the point of splicing-let?

        7 Robust macros: syntax-parse

          7.1 Error-handling strategies for functions

          7.2 Error-handling strategies for macros

          7.3 Using syntax-parse

        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 +

    Copyright (c) 2012-2014 by Greg Hendershott. All rights reserved.
    Last updated 2015-04-21T14:34:53
    Feedback and corrections are welcome here.

    Contents:

        1 Preface

        2 Our plan of attack

        3 Transform!

          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 Pattern variable vs. template—fight!

            4.1.1 with-syntax

            4.1.2 with-syntax*

            4.1.3 format-id

            4.1.4 Another example

          4.2 Making our own struct

          4.3 Using dot notation for nested hash lookups

        5 Syntax parameters

        6 What’s the point of splicing-let?

        7 Robust macros: syntax-parse

          7.1 Error-handling strategies for functions

          7.2 Error-handling strategies for macros

          7.3 Using syntax-parse

        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 @@ -255,8 +255,8 @@ pieces.

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

    ; Nested hasheq's typical of a jsexpr:
    > (define js (hasheq 'a (hasheq 'b (hasheq 'c "value"))))

    In JavaScript you can use dot notation:

    foo = js.a.b.c;

    In Racket it’s not so convenient:

    (hash-ref (hash-ref (hash-ref js 'a) 'b) 'c)

    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 better. 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 ()
          ; 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 +like JavaScript?

    ; This macro:
    > (require (for-syntax racket/syntax))
    > (define-syntax (hash.refs stx)
        (syntax-case stx ()
          ; If the optional default' is missing, use #f.
          [(_ chain)
           #'(hash.refs chain #f)]
          [(_ chain default)
           (let* ([chain-str (symbol->string (syntax->datum #'chain))]
                  [ids (for/list ([str (in-list (regexp-split #rx"\\." chain-str))])
                         (format-id #'chain "~a" str))])
             (with-syntax ([hash-table (car ids)]
                           [keys       (cdr ids)])
               #'(hash-refs hash-table 'keys 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)]
          ; If the optional default' is missing, use #f.
          [(_ chain)
           #'(hash.refs chain #f)]
          [(_ chain default)
           (raise-syntax-error #f "Expected hash.key0[.key1 ...] [default]" stx #'chain)
           (let* ([chain-str (symbol->string (syntax->datum #'chain))]
                  [ids (for/list ([str (in-list (regexp-split #rx"\\." chain-str))])
                         (format-id #'chain "~a" str))])
             ; Check that we have at least hash.key
             (unless (and (>= (length ids) 2)
                          (not (eq? (syntax-e (cadr ids)) '||)))
               (raise-syntax-error #f "Expected hash.key" stx #'chain))
             (with-syntax ([hash-table (car ids)]
                           [keys       (cdr ids)])
               #'(hash-refs hash-table 'keys 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.key0[.key1 ...]

    [default]

      at: js

      in: (hash.refs js #f)

    > (hash.refs js.)

    eval:100:0: hash.refs: Expected hash.key0[.key1 ...]

    [default]

      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. Fortunately we’ll soon see how syntax-parse can help mitigate that, in much the same way as contracts in normal @@ -301,7 +301,7 @@ user thinks they’re calling misuse, but is get message from string-append. In this simple example they could probably guess what’s happening, but in most cases they won’t.

    2. Write some error handling code.

    > (define (misuse s)
        (unless (string? s)
          (error 'misuse "expected a string, but got ~a" s))
        (string-append s " snazzy suffix"))
    ; User of the function:
    > (misuse 0)

    misuse: expected a string, but got 0

    ; I goofed, and understand why! It's a shame the writer of the
    ; function had to work so hard to tell me.

    Unfortunately the error code tends to overwhelm and/or obscure our function definition. Also, the error message is good but not -great. Improving it would require even more error code.

    3. Use a contract.

    > (define/contract (misuse s)
        (string? . -> . string?)
        (string-append s " snazzy suffix"))
    ; User of the function:
    > (misuse 0)

    misuse: contract violation

      expected: string?

      given: 0

      in: the 1st argument of

          (-> string? string?)

      contract from: (function misuse)

      blaming: program

      at: eval:131.0

    ; I goofed, and understand why! I'm happier, and I hear the writer of
    ; the function is happier, too.

    This is the best of both worlds.

    The contract is a simple and concise. Even better, it’s +great. Improving it would require even more error code.

    3. Use a contract.

    > (define/contract (misuse s)
        (string? . -> . string?)
        (string-append s " snazzy suffix"))
    ; User of the function:
    > (misuse 0)

    misuse: contract violation

      expected: string?

      given: 0

      in: the 1st argument of

          (-> string? string?)

      contract from: (function misuse)

      blaming: program

       (assuming the contract is correct)

      at: eval:131.0

    ; I goofed, and understand why! I'm happier, and I hear the writer of
    ; the function is happier, too.

    This is the best of both worlds.

    The contract is a simple and concise. Even better, it’s declarative. We say what we want to happen, not how.

    On the other hand the user of our function gets a very detailed error message. Plus, the message is in a standard, familiar format.

    4. Use Typed Racket.

    #lang typed/racket
    > (: misuse (String -> String))
    > (define (misuse s)
        (string-append s " snazzy suffix"))
    > (misuse 0)

    eval:3:0: Type Checker: type mismatch

      expected: String

      given: Zero

      in: 0

    Even better, Typed Racket can catch usage mistakes up-front at compile time.

    7.2 Error-handling strategies for macros

    For macros, we have similar choices.

    1. Ignore the possibility of misuse. This choice is even worse for diff --git a/index.html b/index.html index 7215111..73bf283 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-07-11T08:56:17
    Feedback and corrections are welcome here.

    Contents:

        1 Preface

        2 Our plan of attack

        3 Transform!

          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 Pattern variable vs. template—fight!

            4.1.1 with-syntax

            4.1.2 with-syntax*

            4.1.3 format-id

            4.1.4 Another example

          4.2 Making our own struct

          4.3 Using dot notation for nested hash lookups

        5 Syntax parameters

        6 What’s the point of splicing-let?

        7 Robust macros: syntax-parse

          7.1 Error-handling strategies for functions

          7.2 Error-handling strategies for macros

          7.3 Using syntax-parse

        8 References and Acknowledgments

        9 Epilogue

     
    \ No newline at end of file +Fear of Macros
    On this page:
    Fear of Macros

    Fear of Macros

    +
    Copyright (c) 2012-2014 by Greg Hendershott. All rights reserved.
    Last updated 2015-04-21T14:34:58
    Feedback and corrections are welcome here.

    Contents:

        1 Preface

        2 Our plan of attack

        3 Transform!

          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 Pattern variable vs. template—fight!

            4.1.1 with-syntax

            4.1.2 with-syntax*

            4.1.3 format-id

            4.1.4 Another example

          4.2 Making our own struct

          4.3 Using dot notation for nested hash lookups

        5 Syntax parameters

        6 What’s the point of splicing-let?

        7 Robust macros: syntax-parse

          7.1 Error-handling strategies for functions

          7.2 Error-handling strategies for macros

          7.3 Using syntax-parse

        8 References and Acknowledgments

        9 Epilogue

     
    \ No newline at end of file diff --git a/manual-style.css b/manual-style.css index d9e1783..fa7e8cc 100644 --- a/manual-style.css +++ b/manual-style.css @@ -88,6 +88,9 @@ p, .SIntrapara { line-height: 1.4; } +.compact { + padding: 0 0 1em 0; +} li { list-style-position: outside; @@ -241,6 +244,12 @@ a:hover { height: 4rem; } +.nosearchform { + margin: 0; + padding: 0; + height: 4rem; +} + .searchbox { font-size: 1rem; width: 12rem; @@ -304,7 +313,20 @@ a:hover { border-left: 0.4rem solid #ccb; } -.refcontent p { + +/* slightly different handling for margin-note* on narrow screens */ +@media all and (max-width:1260px) { + span.refcolumn { + float: right; + width: 50%; + margin-left: 1rem; + margin-bottom: 0.8rem; + margin-top: 1.2rem; + } + +} + +.refcontent, .refcontent p { line-height: 1.5; margin: 0; } diff --git a/pattern-matching.html b/pattern-matching.html index f3897ec..028fd3d 100644 --- a/pattern-matching.html +++ b/pattern-matching.html @@ -1,5 +1,5 @@ -4 Pattern matching: syntax-case and syntax-rules

    4 Pattern matching: syntax-case and syntax-rules

    Most useful syntax transformers work by taking some input syntax, and +4 Pattern matching: syntax-case and syntax-rules

    4 Pattern matching: syntax-case and syntax-rules

    Most useful syntax transformers work by taking some input syntax, and rearranging the pieces into something else. As we saw, this is possible but tedious using list accessors such as cadddr. It’s more convenient and less error-prone to use @@ -110,12 +110,12 @@ pieces.

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

    ; Nested hasheq's typical of a jsexpr:
    > (define js (hasheq 'a (hasheq 'b (hasheq 'c "value"))))

    In JavaScript you can use dot notation:

    foo = js.a.b.c;

    In Racket it’s not so convenient:

    (hash-ref (hash-ref (hash-ref js 'a) 'b) 'c)

    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 better. 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 ()
          ; 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 +like JavaScript?

    ; This macro:
    > (require (for-syntax racket/syntax))
    > (define-syntax (hash.refs stx)
        (syntax-case stx ()
          ; If the optional default' is missing, use #f.
          [(_ chain)
           #'(hash.refs chain #f)]
          [(_ chain default)
           (let* ([chain-str (symbol->string (syntax->datum #'chain))]
                  [ids (for/list ([str (in-list (regexp-split #rx"\\." chain-str))])
                         (format-id #'chain "~a" str))])
             (with-syntax ([hash-table (car ids)]
                           [keys       (cdr ids)])
               #'(hash-refs hash-table 'keys 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)]
          ; If the optional default' is missing, use #f.
          [(_ chain)
           #'(hash.refs chain #f)]
          [(_ chain default)
           (raise-syntax-error #f "Expected hash.key0[.key1 ...] [default]" stx #'chain)
           (let* ([chain-str (symbol->string (syntax->datum #'chain))]
                  [ids (for/list ([str (in-list (regexp-split #rx"\\." chain-str))])
                         (format-id #'chain "~a" str))])
             ; Check that we have at least hash.key
             (unless (and (>= (length ids) 2)
                          (not (eq? (syntax-e (cadr ids)) '||)))
               (raise-syntax-error #f "Expected hash.key" stx #'chain))
             (with-syntax ([hash-table (car ids)]
                           [keys       (cdr ids)])
               #'(hash-refs hash-table 'keys 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.key0[.key1 ...]

    [default]

      at: js

      in: (hash.refs js #f)

    > (hash.refs js.)

    eval:100:0: hash.refs: Expected hash.key0[.key1 ...]

    [default]

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

     
    \ No newline at end of file +a possible choice.

     
    \ No newline at end of file diff --git a/scribble-common.js b/scribble-common.js index c0a32af..1ec7da5 100644 --- a/scribble-common.js +++ b/scribble-common.js @@ -2,8 +2,7 @@ // Page Parameters ------------------------------------------------------------ -var page_query_string = - (location.href.search(/\?([^#]+)(?:#|$)/) >= 0) && RegExp.$1; +var page_query_string = location.search.substring(1); var page_args = ((function(){ @@ -32,13 +31,16 @@ function MergePageArgsIntoLink(a) { } function MergePageArgsIntoUrl(href) { - href.search(/^([^?#]*)(?:\?([^#]*))?(#.*)?$/); - if (RegExp.$2.length == 0) { - return RegExp.$1 + "?" + page_query_string + RegExp.$3; - } else { + var mtch = href.match(/^([^?#]*)(?:\?([^#]*))?(#.*)?$/); + if (mtch == undefined) { // I think this never happens + return "?" + page_query_string; + } + if (!mtch[2]) { + return mtch[1] + "?" + page_query_string + (mtch[3] || ""); + } // need to merge here, precedence to arguments that exist in `a' var i, j; - var prefix = RegExp.$1, str = RegExp.$2, suffix = RegExp.$3; + var prefix = mtch[1], str = mtch[2] || "", suffix = mtch[3] || ""; var args = str.split(/[&;]/); for (i=0; i