[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: Threading / Pipe Macro
From: |
Chris Vine |
Subject: |
Re: Threading / Pipe Macro |
Date: |
Sun, 7 Jul 2019 21:16:13 +0100 |
On Sun, 07 Jul 2019 15:30:36 -0400
Mark H Weaver <address@hidden> wrote:
> Hi Chris,
>
> Chris Vine <address@hidden> 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
Re: Threading / Pipe Macro, Erik Edrosa, 2019/07/07