[racket] anamorphic macros

From: Eli Barzilay (eli at barzilay.org)
Date: Sun Jan 18 22:18:14 EST 2015

[Aggregate reply, since you got replies from all over the place...]

On Sun, Jan 18, 2015 at 7:49 PM, Peter Samarin <petrsamarin at gmail.com> wrote:
> Hi all,
>
> I was working through chapter 16 of "land of lisp" and there is (at
> that point) a buggy split macro defined like this:
>
> (defmacro split (val yes no)
>   `(if ,val
>        (let ((head (car ,val))
>              (tail (cdr ,val)))
>          ,yes)
>        ,no))
>
> Here is my version of the equivalently buggy Racket counterpart:
> [...]

As Neil said, using `eval' is almost always a bad idea.  A genral rule
of thumb is that if you're not sure whether it's a bad idea, then it is.
(Seriously.)

If you really want an unhygienic Racket translation of that macro, then:

    (require compatibility/defmacro)

and then you can use the above macro definition as-is.  But it is
unhygienic, which is still a problem.


On Sun, Jan 18, 2015 at 8:04 PM, Jon Zeppieri <zeppieri at gmail.com> wrote:
> Breaking hygiene with `syntax-case`:
>
> (define-syntax (split stx)
>   (syntax-case stx ()
>     ([split val yes no]
>      (let ([head-stx (datum->syntax stx 'head)]
>            [tail-stx (datum->syntax stx 'tail)])
>        ...))))

Yes, you can do that -- it's kind of similar to what the compatipility
`defmacro' will do for you -- but it's just as bad.

The thing is that Racket has *identifiers*, whereas in Lisp you have
plain symbols.  The difference is that an identifier is kind of like a
symbol + its lexical scope.  If you're coming from common lisp, then you
can think about it as if each newly nested scope has symbols in a new
(anonymous-ish) package.  Keeping that in mind, read
http://barzilay.org/misc/stxparam.pdf to see what's wrong with
unhygienic macros.


On Sun, Jan 18, 2015 at 8:31 PM, Neil Van Dyke <neil at neilvandyke.org> wrote:
> * Just doing it as a procedure that takes closures for the `yes`and
>   `no` arguments (with the `yes` one taking arguments for `head` and
>   `tail`).

That works -- it's often a way to keep most of the functionality in a
function, and then have a macro that deals with the syntax.


> * Doing it as syntax, but having the user of the extension define the
>   .  (This might seem overkill, but maybe that's because that's
>   because the original macro is already overkill.)

Assuming that you're talking about having the user of the macro provide
the identifiers -- it's a common sentiment, but it doesn't work either.
It breaks when you want to use macros in macros (which is exactly where
the unhygienic solutions break too; see the above PDF for an
explanation).


On Sun, Jan 18, 2015 at 8:19 PM, J. Ian Johnson <ianj at ccs.neu.edu> wrote:
> You're better off defining head and tail as syntax parameters and
> defining your macro with the more hygienic syntax-parameterize using
> rename transformers.  [...]

That's the best approach, but since it's still looks as a confusing
issue as ever, it's worth to make this explicit and provide the bits
that Ian skipped:

    #lang racket
    (require racket/stxparam)
    (define-syntax-parameter head
      (lambda (s) (raise-syntax-error 'head "can only be used in a `split'" s)))
    (define-syntax-parameter tail
      (lambda (s) (raise-syntax-error 'tail "can only be used in a `split'" s)))
    (define-syntax-rule (split val yes no)
      (if (empty? val)
        no
        (let ([h (car val)] [t (cdr val)])
          (syntax-parameterize ([head (make-rename-transformer #'h)]
                                [tail (make-rename-transformer #'t)])
            yes))))

Now you can fix the problem of evaluating the value multiple times in a
similar way to LoL:

    #lang racket
    (require racket/stxparam)
    (define-syntax-parameter head
      (lambda (s) (raise-syntax-error 'head "can only be used in a `split'" s)))
    (define-syntax-parameter tail
      (lambda (s) (raise-syntax-error 'tail "can only be used in a `split'" s)))
    (define-syntax-rule (split val yes no)
      (let ([x val])
        (if (empty? x)
          no
          (let ([h (car x)] [t (cdr x)])
            (syntax-parameterize ([head (make-rename-transformer #'h)]
                                  [tail (make-rename-transformer #'t)])
              yes)))))

And that works fine.

-- 
          ((lambda (x) (x x)) (lambda (x) (x x)))          Eli Barzilay:
                    http://barzilay.org/                   Maze is Life!

Posted on the users mailing list.