123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133 |
- ;; The following macro was suggested by Chris Vine on the Guile Scheme user
- ;; mailing list.
- ;; Comments added by me.
- ;; Warning: Apparently the following transcript posted by Mark H. Weaver on the
- ;; Guile Scheme user mailing list shows, that this macro is not hygienic:
- ;; ==========
- ;; TRANSCRIPT
- ;; ==========
- #|
- --8<---------------cut here---------------start------------->8---
- scheme@(guile-user)> (define-syntax ->
- (lambda (x)
- (syntax-case x ()
- [(k exp0 . exps)
- (let* ([reversed (reverse (cons (syntax->datum #'exp0)
- (syntax->datum #'exps)))]
- [out (let loop ([first (car reversed)]
- [rest (cdr reversed)])
- (if (null? rest)
- first
- (let ([func (car first)]
- [args (cdr first)])
- (append `(,func ,@args)
- (list (loop (car rest) (cdr rest)))))))])
- (datum->syntax #'k out))])))
- scheme@(guile-user)> (define t 'global-t)
- scheme@(guile-user)> (define-syntax-rule (foo x)
- (-> x (format #t "[t=~A] ~A\n" t)))
- scheme@(guile-user)> (let ((t 'inner-t)) (foo t))
- [t=global-t] global-t
- $1 = #t
- scheme@(guile-user)>
- --8<---------------cut here---------------end--------------->8---
- I recommend reformulating the -> macro using 'syntax-rules' as follows:
- --8<---------------cut here---------------start------------->8---
- scheme@(guile-user)> (define-syntax ->
- (syntax-rules ()
- ((-> exp)
- exp)
- ((-> exp ... (op args ...))
- (op args ... (-> exp ...)))))
- scheme@(guile-user)> (let ((t 'inner-t)) (foo t))
- [t=global-t] inner-t
- $8 = #t
- scheme@(guile-user)>
- --8<---------------cut here---------------end--------------->8---
- |#
- (define-syntax ~>
- ;; When using `syntax-case`, we need to wrap it in a lambda.
- (lambda (x)
- ;; Look at the input `x` and define no literals.
- (syntax-case x ()
- ;; There is only one case, which differentiates between the thing going
- ;; through the pipe represented by `k`, the first expression `exp0`, and
- ;; remaining expressions as `exps`.
- [(k exp0 . exps)
- (let*
- ([reversed
- ;; We are reversing the given expressions, because the first one
- ;; given is the inner-most in the result of the macro. The
- ;; applications of expressions of the reversed list can be nested
- ;; one after another until none remains and everything is applied
- ;; to `k`.
- (reverse
- ;; Make a normal list out of the pattern matched parts, so that it
- ;; can be reversed using the reverse function.
- (cons (syntax->datum #'exp0)
- (syntax->datum #'exps)))]
- ;; The output of the macro is computed by nesting the operations
- ;; contained within the expressions from the reversed list of
- ;; expressions.
- [out
- ;; We use a named let as loop.
- (let loop
- ;; For an initial value we extract the first expression from
- ;; the reversed list of expressions. It will be the outermost
- ;; wrapping call in the result of the macro.
- ;; In further iterations the loop will one by one move inwards
- ;; towards the first applied expression in the result of the
- ;; macro.
- ([first (car reversed)]
- ;; The next iteration will work with the rest of the
- ;; expressions. Typical list eater behavior.
- [rest (cdr reversed)])
- ;; We check whether there are more expressions in the rest of the
- ;; expressions, to know when to not recur by calling `loop`
- ;; again.
- (if (null? rest)
- ;; In case there are no remaining expressions in the rest of
- ;; expressions to be applied to the macro input, we only need to
- ;; apply the current `first` expression.
- first
- ;; In case there are still expressions, which need to be
- ;; applied, we split up the procedure and the already given
- ;; arguments to the procedure, so that we can insert the `k`
- ;; into the arguments, changing what the procedure is applied
- ;; to.
- (let ([func (car first)]
- [args (cdr first)])
- ;; To the list that is the procedure and its arguments we
- ;; append whatever is produced by the recursive calls of
- ;; the `loop`, thereby wrapping the result of further
- ;; iterations with the procedure call.
- (append `(,func ,@args)
- ;; `append` requires a list, so we need to build a
- ;; list. Since this happens at read-time, the usage
- ;; of append will not affect the performance at
- ;; runtime.
- (list
- ;; Here we recur and use the rest of the list.
- (loop
- ;; The next iteration's first expression is the
- ;; first of the rest of expressions
- (car rest)
- ;; The tail of the rest of expressions will make
- ;; the next iteration's rest of expressions.
- (cdr rest)))))))])
- ;; Wrap `out`, within the lexical context corresponding to the
- ;; identifier `k`. I think this is needed to give out the same scope
- ;; and as the original input to the macro had. It will mean, that in
- ;; that scope the same things are visible and invisible and `out`
- ;; cannot access anything not accessible from where the macro was
- ;; called.
- (datum->syntax #'k out))])))
|