8 Recursive Shapes
8.1 The Datum Shape
The Datum shape contains all number terms, identifier terms, and other atomic terms, as well as all list, vector, hash, box, and prefab struct terms containing Datum elements. That is, Datum contains any term the corresponds to a readable value.
The Datum shape represents the intention to use the term as a literal within a quote expression, or to convert it to a compile-time value using syntax->datum.
There is no syntax class corresponding to Datum.
;; (my-case1 Expr [Datum Expr] ...) : Expr
(my-case1 (begin (printf "got a coin!\n") (* 5 5)) [5 "nickel"] [10 "dime"] [25 "quarter"]) ; expect print once, "quarter"
; (my-case1 Expr [Datum Expr] ...) : Expr (define-syntax my-case1 (syntax-parser [(_ to-match:expr [datum result:expr] ...) #'(let ([tmp to-match]) (cond [(equal? tmp (quote datum)) result] ...))]))
; (my-case1 Expr [Datum Expr] ...) : Expr (define-syntax my-case1 (syntax-parser [(_ to-match:expr [datum result:expr] ...) #'(let ([tmp to-match]) (cond [(equal? tmp datum) result] ...))]))
> (my-case1 (list 123) [(123) "matched"]) application: not a procedure;
expected a procedure that can be applied to arguments
given: 123
> (let-syntax ([#%datum (lambda (stx) (raise-syntax-error #f "no self-quoting!" stx))]) (my-case1 '2 [1 'one] [2 'two] [3 'lots])) eval:7:0: #%datum: no self-quoting!
in: (#%datum . 1)
Exercise 17: Generalize my-case1 to my-case, which has a list of datums in each clause. That is, the macro has the following shape:
;; (my-case Expr [(Datum ...) Expr] ...) : Expr
8.2 Datum as a Recursive Shape
;; SimpleDatum ::= (SimpleDatum ...) | SimpleAtom ;; SimpleAtom ::= Identifier | Number | Boolean | String
Like the corresponding shapes, the simple-datum syntax class is recursive; the simple-atom syntax class is not. Let’s discuss simple-atom first.
(begin-for-syntax ; simple-atom? : Any -> Boolean (define (simple-atom? v) (or (symbol? v) (number? v) (boolean? v) (string? v))) (define-syntax-class simple-atom #:attributes () ; (pattern a #:when (simple-atom? (syntax->datum #'a))) (pattern a #:and (~fail #:unless (simple-atom? (syntax->datum #'a))))))
(begin-for-syntax (define-syntax-class simple-datum #:attributes () (pattern (elem:simple-datum ...)) (pattern atom:simple-atom)))
There are no attributes, because the only interpretation that SimpleDatum supports can be achieved with quote or syntax->datum on the term itself.
8.3 Quasiquotation
;; (my-quasiquote QuasiDatum) : Expr
;; QuasiDatum ::= (escape Expr) ;; | (QuasiDatum ...) ;; | SimpleAtom
(define-syntax escape (lambda (stx) (raise-syntax-error #f "illegal use of escape" stx)))
The error only occurs if the macro is expanded by the Racket macro expander; our macros and syntax classes can still recognize references to it without triggering the error. Note that the module that provides my-quasiquote must also provide escape so that users of my-quasiquote can refer to this escape binding.
(begin-for-syntax (define-syntax-class quasi-datum #:literals (escape) (pattern (escape code:expr)) (pattern (elem:quasi-datum ...)) (pattern a:simple-atom)))
(begin-for-syntax (define-syntax-class quasi-datum #:attributes (code) ; Expr #:literals (escape) (pattern (escape code:expr)) (pattern (elem:quasi-datum ...) #:with code #'(list elem.code ...)) (pattern a:simple-atom #:with code #'(quote a))))
(define-syntax my-quasiquote (syntax-parser [(_ qd:quasi-datum) #'qd.code]))
> (my-quasiquote (1 2 () abc xyz)) '(1 2 () abc xyz)
> (my-quasiquote (1 2 (escape (+ 1 2)))) '(1 2 3)
> (my-quasiquote ((expression (+ 1 2)) (value (escape (+ 1 2))))) '((expression (+ 1 2)) (value 3))
> (my-quasiquote (a (b (c (d (e (f (escape (string->symbol "g"))))))))) '(a (b (c (d (e (f g))))))
> (let ([escape 'piňa-colada]) (my-quasiquote (1 2 (escape (+ 1 2))))) '(1 2 (escape (+ 1 2)))
> (let-syntax ([houdini (make-rename-transformer #'escape)]) (my-quasiquote ((houdini 'jacket) (houdini 'water-tank)))) '(jacket water-tank)
> (my-quasiquote (1 2 (escape 3 4))) '(1 2 (escape 3 4))
(begin-for-syntax (define-syntax-class quasi-datum #:attributes (code) ; Expr #:literals (escape) (pattern (escape ~! code:expr)) (pattern (elem:quasi-datum ...) #:with code #'(list elem.code ...)) (pattern a:simple-atom #:with code #'(quote a))))
> (my-quasiquote (1 2 (escape 3 4))) eval:25:0: my-quasiquote: unexpected term
at: 4
in: (my-quasiquote (1 2 (escape 3 4)))
parsing context:
while parsing quasi-datum
term: (escape 3 4)
location: eval:25:0
while parsing quasi-datum
term: (1 2 (escape 3 4))
location: eval:25:0
> (my-quasiquote (1 2 3 4 5)) '(1 2 3 4 5)
(begin-for-syntax (define-syntax-class quasi-datum #:attributes (const? ; Boolean code) ; Expr #:literals (escape) (pattern (escape code:expr) #:attr const? #f) (pattern (elem:quasi-datum ...) #:attr const? (andmap values (datum (elem.const? ...))) #:with code (if (datum const?) #'(quote (elem ...)) #'(list elem.code ...))) (pattern a:simple-atom #:attr const? #t #:with code #'(quote a))))
Exercise 18: Implement quasi-datum and my-quasiquote (the unoptimized version) according to the empty interface strategy and a recursive my-quasiquote macro. Is it possible to implement the quote optimization using this approach?
;; QuasiDatum ::= ... | (cellophane QuasiDatum) A cellophane wrapper is simply discarded from the constructed value; that is, the QuasiDatum (cellophane qd) is equivalent to qd. For example:
(my-quasiquote (1 2 (cellophane 3) (escape (* 2 2)))) ; expect '(1 2 3 4) (my-quasiquote (1 (cellophane 2) (cellophane (cellophane 3)))) ; expect '(1 2 3) Start with the unoptimized version of the quasi-datum syntax class using the macro behavior strategy. After you have updated (and tested) that version, implement a similar optimization for the updated QuasiDatum shape. For example, the second example above should expand directly to (quote (1 2 3)). (Hint: What assumption made by the original optimization does the updated shape violate?)