[racket] for/hash: bad syntax in: for/hash [and 1 more messages]

From: Eli Barzilay (eli at barzilay.org)
Date: Wed Jan 4 16:50:49 EST 2012

11 hours ago, Marijn wrote:
> It took me a while to understand what you're saying here, but if I
> am not mistaken you're saying that the result of the macro-expansion
> is a function-application with `let*' (or `for/hash') in the
> function position. Then when #%app looks that name up in the
> environment it doesn't find anything and thus complains about
> it. What I would then expect is that it complains about an undefined
> variable `let*'.

There is no such lookup at runtime.  Your code *does* expand into a
`let*' (or to `for/hash' in the original case), it just expands into a
non-parenthesized reference to it, which invokes the macro transformer
with just the identifier -- same as if you'd write (+ let* 3).


> > 5. And BTW, the reason it doesn't work is that the first expansion 
> > gets to
> > 
> > ((with-variables let*) (with-variables ((y 1))) (with-variables
> > 2))
> > 
> > and this form is not a macro -- so it's expanded as a function 
> > call,
> > 
> > (#%app ...same...)
> > 
> > and the rest is obvious.
> 
> It's not so obvious to me why this should degenerate into a function
> call; I'd say it depends on the result of (with-variables let*).

This is not a kind of "degeneration".  Here's a possibly simpler to
follow example:

  -> (define-syntax foo
       (syntax-rules ()
         [(foo (x y z)) ((foo x) (foo y) (foo z))]
         [(foo x) x]))
  -> (foo (+ 1 2))
  3
  -> (foo (when 1 2))
  ; stdin:19:6: when: bad syntax in: when [,bt for context]

Here's what happens when macro expansion sees (foo (+ 1 2)):

  1. It recognizes it as a use of the `foo' macro, and calls your
     expander which returns

       ((foo +) (foo 1) (foo 2))

  2. Since this is not a macro (it doesn't begin with a known (or any)
     identifier), it (roughly) interprets it as a function call, which
     in Racket means that you now have:

       (#%app (foo +) (foo 1) (foo 2))

  2a. More accurately, there is a step here of expanding the `#%app'
      form, which can be different in other languages.

  3. It now continues to expand the subexpressions which results in a

       (#%app + 1 2)

     which is the same as what you'd get if you started with just
     (+ 1 2), so this works as expected.

  3a. There are also some steps that this simplifies over: expanding
      (foo +) really returns `+' which is then tested again to see if
      it's a macro -- it's not so that's the final answer there.  But
      with 1 you also get the "special" `#%datum' macro in the
      process.

Now, if you start with (foo (when 1 2)) instead, it would go in
exactly the same way, and end up with

  2. (#%app (foo when) (foo 1) (foo 2))

  3. It now continues to expand the first subexpression, which returns
     `when'.  The (3a) step is important here: in this case `when'
     *is* a macro, so it calls its transformer, handing it the whole
     expression which is ... just `when'.  This means that the best
     that the macro can do is throw an obscure "bad syntax" error with
     this whole expression -- which is the confusing error you see.

At this point it's probably tempting to say that the `foo' macro
behaves well only when you hand it expressions that do not involve
macros.  This is a dangerous assumption, since there might be forms
that look like plain applications but are really macro-expanded --
like applying a contracted function (which is, IIRC, implemented as a
macro to give source information).  In these cases you'd lose such
special treatment.

But an even more subtle point is when you mix languages: the above
seemingly fine case of (foo (+ 1 2)) can be broken since it uses
whatever application means in whatever module the `foo' definition
appears at.  If, for example, you use it in a language that has a
different meaning for applications (like the lazy language), then the
result will be confusing in that you'd get the meaning from `foo's
module.


6 hours ago, Marijn wrote:
> Okay, so I thought about this some more... and while I still think
> this is weird behaviour, for my current code it is rather academic,
> as it would just fail with unexpanded macro-calls in the arguments
> of let*. Which means that I've again fallen into the trap of not
> thinking hard enough about how macros are different than
> functions.

Actually, I'd rephrase it into what I said earlier: if you implement a
code walker, then you're very likely doing something wrong.  A proper
code walker would need to account for those "arguments" (I'm assuming
that you mean macro arguments = sub-expressions) of `let*' and all the
other many relatives, but you also need to worry about internal
definitions, `set!'s, all of the quoting forms, etc etc -- and since
the language is extensible, there's no way to cover all cases anyway.

If you'd really want to do that, then what I wrote earlier applies:
you really want to first fully-expand the code, then use
`kernel-syntax-case' over the resulting code which now has a known
small set of possible forms.  This would not be easy, but it's at
least doable.

But my guess is that it's an overkill for your needs.


> Basically, my idea was to transform:
> 
>   (dependent-boxes
>    ((a)
>     (b (* 2 a))
>     (c (+ 2 a b))))
> 
> into something which would automatically propagate changes in any
> cell/box to any other box which was dependent on it. Pretty much
> exactly like a spreadsheet does.

I think that this should be doable with defining the bindings as
identifier macros -- that's my guess for the relevant system feature
that you're missing (which has lead you to a code walker).

but I think that Matthias's suggestion to look at frtime is more
relevant.  (Sounds like it's doing what you want, and possibly more --
since any expressions are updated, not just ones that are named
explicitly.)  If you really want to do your own thing, then you should
at least look at how the frtime language is implemented.


> As target for that transformation I had chosen something like:
> 
>   (letrec
>       ((value-store (mlist (mcons 'a #f) (mcons 'b #f) (mcons 'c #f)))
>        (rule-store (make-hash
> 		    `((a . ,(lambda () ""))
> 		      (b . ,(lambda () (* 2 (value-store-ref 'a))))
> 		      (c . ,(lambda () (+ 2 (value-store-ref 'a) (value-store-ref
> 'b)))) ))))
>     ...)
> 
> together with surrounding structure such that values could be
> entered and read and such that for each value-change, dependent
> boxes would be updated automatically.

BTW, there's no need for a `mlist' for that -- I think that

  (list (cons 'a (box #f)) (cons 'b (box #f)) (cons 'c (box #f)))

would be better, or just using hash tables (which you'll probably need
to chain since you'll want sharing for scopes).

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


Posted on the users mailing list.