Splicing the result of one macro into another

57 views
Skip to first unread message

Brian Adkins

unread,
Aug 20, 2019, 6:13:21 PM8/20/19
to Racket Users
Consider the following two macros:

(require (for-syntax syntax/parse))

(define-syntax (phone-numbers stx)
  (syntax-parse stx
    [(_ ((~literal number) phone) ...)
     #'(list phone ...)]))

(define x (phone-numbers
           (number "1212")
           (number "2121"))) ; ==> '("1212" "2121")

(define-syntax (add-prefix stx)
  (syntax-parse stx
    [(_ prefix ((~literal number) str) ...)
     #'(list (list 'number (string-append prefix str)) ...)]))

(define y (add-prefix "555"
                      (number "1212")
                      (number "2121"))) ; ==> '((number "5551212") (number "5552121"))

I would like to be able to do the following:

(phone-numbers
 (add-prefix "555"
             (number "1212")
             (number "2121"))
 (number "1234")) ; ==> '("5551212" "5552121" "1234")

I was hoping it would be possible to do this without modifying the phone-numbers macro. In other words, to have the result of expanding add-prefix macro call be:

(number "5551212") (number "5552121")

So that it would appear to the phone-numbers macro as if the user had actually typed:

(phone-numbers
  (number "5551212")
  (number "5552121")
  (number "1234"))

Is it possible to do this w/o the explicit cooperation of the phone-numbers macro?

Brian Adkins

Sorawee Porncharoenwase

unread,
Aug 20, 2019, 8:43:07 PM8/20/19
to Brian Adkins, Racket Users
  1. You will need a cooperation of phone-numbers macro. There are two ways I am aware of
    1.1 You could hard code in phone-numbers to deal with add-prefix directly.
    1.2 A more general approach is to use local-expand in phone-numbers to partially expand macros in the body of phone-numbers. In this case, this is to partially expand add-prefix to something that phone-numbers can recognize and deal with.
  2. The macro add-prefix is weird. It doesn’t expand to the compile-time macro number. Instead, it expands to a runtime function application (to construct a list of lists) and perform the string-append at runtime. This means it would be really hard for phone-numbers to process the partially expanded syntax if you were to use approach 1.2. Are you sure that this behavior of add-prefix is what you want?

Here’s my attempt to solve this problem, while trying to maintain the external behavior you described.

#lang racket

(require (for-syntax syntax/parse))

;; number macro is needed so that "bare" add-prefix works correctly

(define-syntax (number stx)
  (syntax-parse stx
    [(_ x:string) #'(list 'number x)]))

(number "123") ; ==> '(number "123")

;; - if the immediate macro is `number`, don't partially expand it, because
;;   `phone-numbers` know how to deal with it already. Note that
;;   the result attribute should be of ellipsis depth 1, so we need to do
;;   some normalization here.
;;
;; - otherwise, partially expand the immediate macro. The protocol is that 
;;   the immediate macro should expand to (list (number xxx) ...).
;;   We then extract the result from it.

(begin-for-syntax
  (define-syntax-class do-expand
    #:attributes ([result 1])
    (pattern ({~literal number} _)
             #:with (result ...) (list this-syntax))
    (pattern _
             #:with ({~literal list} result ...)
                    (local-expand this-syntax 'expression #f))))

(define-syntax (phone-numbers stx)
  (syntax-parse stx
    [(_ :do-expand ...)
     (syntax-parse #'((~@ result ...) ...)
       [(((~literal number) phone) ...)
        #'(list phone ...)])]))

(phone-numbers
 (number "1212")
 (number "2121")) ; ==> '("1212" "2121")

;; add-prefix computes at compile-time. It abides the protocol described above.

(define-syntax (add-prefix stx)
  (syntax-parse stx
    [(_ prefix ((~literal number) str) ...)
     #:with (prefixed ...) (map (λ (s)
                                  (datum->syntax
                                   this-syntax
                                   (string-append (syntax-e #'prefix)
                                                  (syntax-e s))))
                                (attribute str))
     #'(list (number prefixed) ...)]))

(add-prefix "555"
            (number "1212")
            (number "2121")) ; ==> '((number "5551212") (number "5552121"))

;; add-suffix computes at compile-time. It abides the protocol described above.

(define-syntax (add-suffix stx)
  (syntax-parse stx
    [(_ prefix ((~literal number) str) ...)
     #:with (suffixed ...) (map (λ (s)
                                  (datum->syntax
                                   this-syntax
                                   (string-append (syntax-e s)
                                                  (syntax-e #'prefix))))
                                (attribute str))
     #'(list (number suffixed) ...)]))

(add-suffix "555"
            (number "1212")
            (number "2121")) ; ==> '((number "1212555") (number "2121555"))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(phone-numbers
 (add-prefix "555"
             (number "1212")
             (number "2121"))
 (number "1234"))

;; similar to typing

(phone-numbers
 (list (number "5551212") (number "5552121"))
 (number "1234"))

;; ==> '("5551212" "5552121" "1234")


--
You received this message because you are subscribed to the Google Groups "Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to racket-users...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/racket-users/e6e5a407-7547-44f1-b8c2-bbe7906ed6f5%40googlegroups.com.

Ben Greenman

unread,
Aug 20, 2019, 8:45:27 PM8/20/19
to Brian Adkins, Racket Users
If you have a list at expansion time, you can splice it into a syntax
object. It's not straightforward to get a list from `add-prefix` but
here is one way:

(define-syntax (do-it stx)
#`(phone-numbers
#,@(eval
(local-expand
#'(add-prefix "555"
(number "1212")
(number "2121")) 'expression '())
(make-base-namespace))
(number "1234")))

(do-it)
; ==> '("5551212" "5552121" "1234")

I hope this helps you find something better.

Brian Adkins

unread,
Aug 20, 2019, 9:11:54 PM8/20/19
to Racket Users
Thanks. 

The reason the add-prefix macro is weird is because I have no idea what I'm doing with respect to macros, so I'm currently grasping at straws and experimenting :) I appreciate all of the Racket documentation, but I think there is a need for a much better presentation of the material in a graduated way. Racket School was great, but we jumped into the deep end pretty quickly, and the techniques seem "hacky" with odd idiosyncrasies and clever tricks vs. proceeding from a solid foundation and building upon that.

I'm kind of wondering if it wouldn't be easier to just process the syntax object directly - at least then, I'd be in a realm I'm very familiar with.

I'll review the code you've provided.

Brian Adkins

unread,
Aug 20, 2019, 9:20:51 PM8/20/19
to Racket Users
By the way Sorawee, you've been very helpful in your responses to my mailing list questions, and you seem to have an excellent grasp of macros. How did you acquire your knowledge of macros? 

Sorawee Porncharoenwase

unread,
Aug 21, 2019, 3:36:14 AM8/21/19
to Brian Adkins, Racket Users
I appreciate all of the Racket documentation, but I think there is a need for a much better presentation of the material in a graduated way.

I think what you are saying is, there should be more Racket Guide content, and I think everyone agrees on that...
 
you seem to have an excellent grasp of macros.

Well, not really. There are a lot of stuff I don't know!
 
How did you acquire your knowledge of macros? 

Mostly from reading tutorials and public code:
- https://www.greghendershott.com/fear-of-macros/
- https://www.hashcollision.org/brainfudge/
- https://docs.racket-lang.org/guide/macros.html
- https://docs.racket-lang.org/syntax/stxparse.html
- https://beautifulracket.com
- https://docs.racket-lang.org/syntax-parse-example/

I also attended Racket School in 2017, though as I understand the topic of macro was focused less compared to later years -- if you attended Racket school in recent years, you probably got the knowledge more than I did.

For more advanced topics, I learned a lot from Alexis's blog posts (https://lexi-lambda.github.io). Ultimately there are papers and reports like http://www.cs.utah.edu/plt/scope-sets/ which is a lot harder to read but would be rewarding once you understand it.

Mailing list and Slack are very helpful to me, but the answers are very easy to get lost (including this very thread, perhaps).

--
You received this message because you are subscribed to the Google Groups "Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to racket-users...@googlegroups.com.

Michael Ballantyne

unread,
Aug 22, 2019, 3:26:40 PM8/22/19
to Racket Users
Here's another implementation you might consider. In this version you can view the body of `phone-numbers` as a language consisting of the `number` and `number-list` syntactic forms. The language also supports its own kind of macros---phone number macros---that can expand to forms of the `phone-numbers` body language. `number` and `add-prefix` aren't allowed outside of `phone-numbers`, because they're thought of as a part of that language rather than Racket expressions on their own. `expand+extract-number` is a tiny macro expander for the language.

```
#lang racket

(require (for-syntax syntax/parse syntax/apply-transformer))

(define-syntax number
  (lambda (stx)
    (raise-syntax-error #f "number can only be used in phone-numbers syntax" stx)))
(define-syntax number-list
  (lambda (stx)
    (raise-syntax-error #f "number-list can only be used in phone-numbers syntax" stx)))

(begin-for-syntax
  (struct phone-number-macro (proc)
    #:property prop:procedure
    (lambda (s stx) (raise-syntax-error #f "phone number macros can only be used in phone-numbers syntax" stx))))

(define-syntax (phone-numbers stx)
  (define (expand+extract-number stx)
    (syntax-parse stx
      #:literals (number number-list)
      [(number s:string)
       (list #'s)]
      [(number-list num ...)
       (apply append (map expand+extract-number (syntax->list #'(num ...))))]
      [(head . rest)
       #:do [(define v (syntax-local-value #'head (lambda () #f)))]
       #:when (phone-number-macro? v)
       (expand+extract-number
        (local-apply-transformer (phone-number-macro-proc v)
                                 this-syntax
                                 'expression
                                 '()))]))

  (syntax-parse stx
    [(_ num ...)
     (define expanded-nums
       (apply append (map expand+extract-number (syntax->list #'(num ...)))))
     #`(list #,@expanded-nums)]))

(phone-numbers
 (number "123")
 (number-list (number "123")))

(define-syntax add-prefix
  (phone-number-macro
   (syntax-parser

     [(_ prefix ((~literal number) str) ...)
      #:with (prefixed ...) (map (λ (s)
                                   (datum->syntax
                                    this-syntax
                                    (string-append (syntax-e #'prefix)
                                                   (syntax-e s))))
                                 (attribute str))
      #'(number-list (number prefixed) ...)])))


(phone-numbers
 (add-prefix "555"
             (number "1212")
             (number "2121"))
 (number "1234"))
```
Reply all
Reply to author
Forward
0 new messages