On Sun, 07 Jul 2019 15:30:36 -0400 Mark H Weaver <m...@netris.org> wrote: > Hi Chris, > > Chris Vine <vine35792...@gmail.com> writes: > > > I have a pipeline macro which sort-of mimics ML's |> pipeline operator > > which I use a lot: > > > > (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) (cdrrest)))))))]) > > (datum->syntax #'k out))]))) > > > > Because all the macro does is to rearrange input forms, this is hygienic > > without the need to manipulate syntax objects - you can convert to a datum, > > rearrange and then convert back to a syntax object again, as above. > > This macro is *not* hygienic. The calls to 'syntax->datum' strip all of > the context information from the syntax objects, and then build a new > expression using raw S-expressions. The result is essentially the same > as if you used 'define-macro'. This results various problems. > > For example: > > --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--- > > This macro is hygienic, and also easier to comprehend (IMO). Of course, > it could also be implemented using syntax-case. The key is to always > work with the syntax objects. > > Whenever you use 'syntax->datum' on expressions that are not purely > literals, you will be sacrificing hygiene.
How strange. Both your and my macro gives 'global-t' when I test them, which is the result I would expect. (Maybe I am missing something here, but a result of 'inner-t' would seem to me to imply unhygiene.) However if I change my macro to manipulate syntax objects I do get 'inner-t' (define-syntax --> (lambda (x) (syntax-case x () [(_ exp0 exp1 ...) (let ([reversed (reverse #'(exp0 exp1 ...))]) (with-syntax ([out (let loop ([first (car reversed)] [rest (cdr reversed)]) (if (null? rest) first (syntax-case first () [(func arg0 ...) (append #'(func arg0 ...) (list (loop (car rest) (cdr rest))))])))]) #'out))]))) I need to think more about this and/or reproduce this later. This is with guile-2.2.6 by the way. Chris