The ellipsis seems to work in the most obvious way, but it is still difficult to understand for me. Below I will explain my current understanding and provide a naive translation to map
, but there are quite a few examples do not follow this translation. So I hope someone could clarify the precise behavior of ellipsis.
Intuitively, ellipsis works like list
and map
, for example,
(define-syntax-rule (foo arg ...)
(list '(+ 42 arg ...)))
I look the arg ...
in the pattern as a list (give it a notation arg*
) and look arg ...
in the template as map
. So the foo
macro can be informally translated to:
`(list (+ 42 ,@(map (λ (arg) arg) arg*)))
When the macro be called,
(syntax->datum (expand #'(foo 1 2 3)))
;; (#%app list '(+ 42 1 2 3))
It is equivalent to
`(list (+ 42 ,@(map (λ (arg) arg) '(1 2 3))))
;; '(list (+ 42 1 2 3))
It works as expected.
The 2nd example,
(define-syntax-rule (bar arg ...)
(list '(+ 42 arg) ...))
(syntax->datum (expand #'(bar 1 2)))
;; '(#%app list '(+ 42 1) '(+ 42 2))
It can be translated to:
`(list ,@(map (λ (arg) `(+ 42 ,arg)) arg*))
`(list ,@(map (λ (arg) `(+ 42 ,arg)) '(1 2)))
;; '(list (+ 42 1) (+ 42 2))
The 3rd example,
(define-syntax-rule (baz (arg1 ...) (arg2 ...) )
(list '(+ 42 arg1 arg2) ...))
(syntax->datum (expand #'(baz (1 2) (11 22))))
;; '(#%app list '(+ 42 1 11) '(+ 42 2 22))
There is two ...
in the pattern, so use map
with varargs.
It can be translated to:
`(list ,@(map (λ (arg1 arg2) `(+ 42 ,arg1 ,arg2)) arg1* arg2* ))
`(list ,@(map (λ (arg1 arg2) `(+ 42 ,arg1 ,arg2)) '(1 2) '(11 22)))
;; '(list (+ 42 1 11) (+ 42 2 22))
N.B. The following macro is illegal, that is nice.
(define-syntax-rule (baz arg1 ... arg2 ...)
(list '(+ 42 arg1 arg2) ...))
The 4th example,
(define-syntax-rule (quz (arg1 ...) (arg2 ...) )
(list '(+ 42 arg1 ...)))
(syntax->datum (expand #'(quz (1 2 3) (11 22 33))))
;; '(#%app list '(+ 42 1 2 3))
It can be translated to:
`(list (+ 42 ,@(map (λ (arg1 arg2) arg1) arg1* arg2*)) )
`(list (+ 42 ,@(map (λ (arg1 arg2) arg1) '(1 2 3) '(11 22 33))) )
;; '(list (+ 42 1 2 3))
The 5th example,
(define-syntax-rule (quux (arg1 ...) (arg2 ...) )
(list '(+ 42 arg1 ...) (+ 43 arg2 ...)))
(syntax->datum (expand #'(quux (1 2 3) (11 22 33))))
;; '(#%app list '(+ 42 1 2 3) (#%app + '43 '11 '22 '33))
It can be translated to:
`(list (+ 42 ,@(map (λ (arg1 arg2) arg1) arg1* arg2*))
(+ 43 ,@(map (λ (arg1 arg2) arg2) arg1* arg2*)))
`(list (+ 42 ,@(map (λ (arg1 arg2) arg1) '(1 2 3) '(11 22 33)))
(+ 43 ,@(map (λ (arg1 arg2) arg2) '(1 2 3) '(11 22 33))))
;; '(list (+ 42 1 2 3) (+ 43 11 22 33))
The 6th example,
(define-syntax-rule (quuz (arg1 ...) (arg2 ...) )
(list '(+ 42 arg1 arg2 ...) ...))
(syntax->datum (expand #'(quuz (1 2) (11 22))))
;; '(#%app list '(+ 42 1 11 22) '(+ 42 2 11 22))
It can be translated to:
`(list ,@(map (λ (arg1 arg2) `(+ 42 ,arg1 ,@(map (λ (arg1 arg2) arg2) arg1* arg2*))) arg1* arg2* ))
`(list ,@(map (λ (arg1 arg2) `(+ 42 ,arg1 ,@(map (λ (arg1 arg2) arg2) '(1 2) '(11 22)))) '(1 2) '(11 22) ))
;; '(list (+ 42 1 11 22) (+ 42 2 11 22))
The 7th example,
(define-syntax-rule (corge (x y) ... )
(list (list x ... y ...)
(list x y) ...))
(syntax->datum (expand #'(corge (1 2) (11 22))))
;; '(#%app list (#%app list '1 '11 '2 '22)
;; (#%app list '1 '2) (#%app list '11 '22))
It can be translated to:
`(list (list ,@(map (λ (e) (car e)) (x y)*)
,@(map (λ (e) (cadr e)) (x y)*)
,@(map (λ (e) (list (car e) (cadr e))) (x y)*)))
`(list (list ,@(map (λ (e) (car e)) '((1 2) (11 22)))
,@(map (λ (e) (cadr e)) '((1 2) (11 22))))
,@(map (λ (e) (list (car e) (cadr e))) '((1 2) (11 22)) ))
;; '(list (list 1 11 2 22) (1 2) (11 22))
Following this naive translation, I guess the behavior of the ellipsis is:
When a pattern matches a syntax object, Racket will collect all the matched argN*
in the same level into a list (I said "the same level", because I don't know how nested ellipsis work in pattern, e.g. ((a b ...) ...))
and how they interact with ...
in template.
When instantiating a template, when encountering a ...
, Racket will apply map
to the list which previous collected and a lambda which constructs syntax object before ...
in the template by selecting corresponding pattern variables..
The first question: Is this explanation correct?
I have some counterexamples that violate this explanation.
Counterexamples 1:
(syntax->datum (expand #'(quuz (1 2) (11 22 33))))
;; '(#%app list '(+ 42 1 11 22 33) '(+ 42 2 11 22 33))
But the naive translation version report error:
`(list ,@(map (λ (arg1 arg2) `(+ 42 ,arg1 ,@(map (λ (arg1 arg2) arg2) '(1 2) '(11 22 33)))) '(1 2) '(11 22 33)))
; ERROR: map: all lists must have same size
So the underlying algorithm of ellipsis is not using the standard map
.
Counterexamples 2:
(define-syntax-rule (ce1 (arg1 ...) (arg2 ...) )
(list '(+ 42 arg2 ...) ...))
;; ERROR: too many ellipses in template
However, following the naive translation, it should not complain.
`(list ,@(map (λ (arg1 arg2) `(+ 42 ,@(map (λ (arg1 arg2) arg2) arg1* arg2*))) arg1* arg2* ))
`(list ,@(map (λ (arg1 arg2) `(+ 42 ,@(map (λ (arg1 arg2) arg2) '(1 2) '(11 22) ))) '(1 2) '(11 22) ))
;; '(list (+ 42 11 22) (+ 42 11 22))
Comparing with the 6th example, the Counterexamples 2 just lacks arg1
in the body.
Because of these two counterexamples, I think my conjecture was not correct.
Can someone explain the precise behavior of ellipsis? Or correcting my mistake.
Thanks.