[Top][All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Threading / Pipe Macro

From: Mark H Weaver
Subject: Re: Threading / Pipe Macro
Date: Sun, 07 Jul 2019 15:30:36 -0400
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/26.1 (gnu/linux)

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)
                         (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
--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 ... (op args ...))
                          (op args ... (-> exp ...)))))
scheme@(guile-user)> (let ((t 'inner-t)) (foo t))
[t=global-t] inner-t
$8 = #t
--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.


reply via email to

[Prev in Thread] Current Thread [Next in Thread]