guile-user
[Top][All Lists]
Advanced

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

Re: Threading / Pipe Macro (Chris Vine, Mark H Weaver)


From: Zelphir Kaltstahl
Subject: Re: Threading / Pipe Macro (Chris Vine, Mark H Weaver)
Date: Mon, 8 Jul 2019 23:10:28 +0200
User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Thunderbird/60.7.1

Hello Chris and hello Mark,

Thank you both for posting your macros! This is really useful and I am
looking forward to using this in the next situation where there would be
deep nesting or where it seems appropriate in other ways.

To understand what is going on in the macro, I wrote a lot of explaining
comments and uploaded it in a new repository, where I intend to keep
macros I come across and explain them, if I can:

https://gitlab.com/ZelphirKaltstahl/guile-scheme-macros/blob/master/threading-pipe-macro/macro.scm

If there are mistakes in my explanations, it would be great if you could
point out the mistakes : ) I also think it should be easily doable for
me to change for example where the piped argument is inserted in the
list of arguments. Instead of `append` I could probably put the list
built by recurring in between the `func` and `args` to insert in the
first position.

Not sure what kind of license I should put on it (?) Usually I put
everything under GPL, but I am not sure how it is when the code is
actually from someone else.

Thanks!

Zelphir


On 7/8/19 12:26 AM, address@hidden wrote:
> Message: 4
> Date: Sun, 7 Jul 2019 21:16:13 +0100
> From: Chris Vine <address@hidden>
> To: Mark H Weaver <address@hidden>
> Cc: address@hidden
> Subject: Re: Threading / Pipe Macro
> Message-ID: <address@hidden>
> Content-Type: text/plain; charset=US-ASCII
>
> 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
>
>
>
> ------------------------------
>
> Message: 5
> Date: Sun, 7 Jul 2019 23:25:13 +0100
> From: Chris Vine <address@hidden>
> To: Mark H Weaver <address@hidden>
> Cc: address@hidden
> Subject: Re: Threading / Pipe Macro
> Message-ID: <address@hidden>
> Content-Type: text/plain; charset=US-ASCII
>
> On Sun, 7 Jul 2019 21:16:13 +0100
> Chris Vine <address@hidden> wrote:
>> I need to think more about this and/or reproduce this later.
>>
>> This is with guile-2.2.6 by the way.
> OK, I did set up the test of your macro incorrectly (the one using
> syntax-rules): tested properly, as you say it produces
>
>   [t=global-t] inner-t'.
>
> So does my revised syntax-case version which manipulates syntax objects.
> I also agree that that is the answer that should be produced.  My
> version was hygienic when used in functions, but not when used as a
> macro in a macro.
>
> Chris
>
>
>
> ------------------------------
>
> Message: 6
> Date: Sun, 07 Jul 2019 18:24:32 -0400
> From: Mark H Weaver <address@hidden>
> To: Chris Vine <address@hidden>
> Cc: address@hidden
> Subject: Re: Threading / Pipe Macro
> Message-ID: <address@hidden>
> Content-Type: text/plain
>
> Hi Chris,
>
> Here's a complete, unedited transcript with Guile 2.2.6:
>
> --8<---------------cut here---------------start------------->8---
> mhw@jojen ~$ guile
> GNU Guile 2.2.6
> Copyright (C) 1995-2019 Free Software Foundation, Inc.
>
> Guile comes with ABSOLUTELY NO WARRANTY; for details type `,show w'.
> This program is free software, and you are welcome to redistribute it
> under certain conditions; type `,show c' for details.
>
> Enter `,help' for help.
> 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)> (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
> $2 = #t
> scheme@(guile-user)>
> --8<---------------cut here---------------end--------------->8---
>
> Chris Vine <address@hidden> writes:
>> How strange.  Both your and my macro gives 'global-t' when I test them,
> Can you show me a complete, unedited transcript that demonstrates what
> you're seeing?
>
>> 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.)
> (foo EXPR) is supposed to print "[t=global-t] VAL", where VAL is the
> result of evaluating EXPR.  With this in mind,
>
>     (let ((t 'inner-t))
>       (foo t))
>
> The argument to 'foo' here should refer to the lexical binding of 't',
> i.e. the variable with value 'inner-t'.  I'm curious what would make you
> expect otherwise.
>
> On the other hand, the reference to 't' in the template of the 'foo'
> macro should refer to the toplevel variable 't', because the template
> does not appear within the 'let'.
>
> This is a good example of why syntax objects are needed, to distinguish
> between these two references to distinct variables named 't'.  When you
> convert the references to datums, the distinctions are lost.
>
>      Regards,
>        Mark



reply via email to

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