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

Re: semantics of ?=



In article <200209151605.g8FG5xML008065@life.ai.mit.edu>,
 Gabor Greif <gabor@mac.com> wrote:

> We had quite some discussion on IRC channel #dylan about what the 
> intensional name capture by ?= should accomplish.

Indeed.


> My idea is: ?=name in a macro expansion behaves exactly the same _as_if_ 
> the users code had \name in that position.

Absolutely.  The question is: who is the user?

I believe that the user is the person who typed in the call to the 
macro, A, whose expansion contains a use of ?=.

Gabor seems to believe that the user is the person who types in a call 
of a macro C, which in its expansion uses macro B, which in it's 
expansion uses macro A, which in its expansion uses ?=.

In my opinion, Gabor's intepretation violates several basic tenets:

- the principle of least surprise.

- the principle that a macro that evaluates each of its arguments 
exactly once, in the appropriate order, has the same semantics as a 
function with the same body.

[snipped examples of how Gwydion Dylan currently works, and indeed how 
it would *continue* to work under my interpretation]

> ------------------------------------------------------------
> 
> Now, Bruce hoped to be able to refer to a macro-mentioned hygienic name 
> by using ?=.
> 
> Something like this:
> 
> begin
>    let it = 5;
>    format-out("captured: %d\n", it-binder(42, it-capturer2()));
> end;
> 
> My opinion is that this should _never_ result in "captured: 42".

I agree totally.  That should never work.  It is, however, *quite* 
different to what I want to work.  it-capturer2, when expanded, refers 
to a name "it" in the scope into which it is expanded -- namely code 
written by the person who typed this begin/end block.  it-binder makes 
no attempt to refer to the same scope.


What I want to have work can not be expressed by Gabor's macros.

Allow me to give an example smaller than the previous one I posted here.  
I suspect that there has so far been very little use of intentional 
violation of hygiene in Dylan.  Even in Common Lisp, where it is the 
default and has to be specifically avoided, it seems that a rather small 
minority of macros intentionally use variable capture -- and most of 
those are the "anaphoric" macros described in Chapter 14 of _On Lisp_ 
(http://www.paulgraham.com/onlisptext.html).

>From P191:

  (defmacro awhile (expr &body body)
    `(do ((it ,expr ,expr))
         ((not it))
       ,@body))


  (awhile (poll *fridge*)
    (eat it))

In Dylan this will be:

  define macro awhile
    {while (?:expr) ?:body end} =>
      {for (?=it = ?expr then ?expr,
            while: ?=it)
         ?body
       end}
  end macro;

  awhile(poll($fridge))
    eat(it)
  end

Presumably it would be reasonable to assume that this should work 
properly in Dylan.  Let's fill in the details so we can run it and see:

---------------------------------------
module: capture

define macro awhile
  {awhile (?exp:expression) ?:body end} =>
    {for (?=it = ?exp then ?exp,
          while: ?=it)
       ?body
     end}
end macro;

define variable $fridge = as(<deque>, #("pizza", "steak", "yoghurt"));

define method poll(x)
  ~x.empty? & x.pop
end;

define method eat(x)
  format-out("Eating %s\n", x);
end;

awhile(poll($fridge))
  eat(it)
end
---------------------------------------
bruce@k7:~/programs/dylan/capture > ./capture 
Eating pizza
Eating steak
Eating yoghurt
bruce@k7:~/programs/dylan/capture > 
---------------------------------------

Works fine.

Now, it would be reasonable to use awhile() in a function, rather than 
at the top level:

  define method pig-out(where)
    awhile(poll(where))
      eat(it)
    end
  end;

  pig-out($fridge);

Works fine.  Try it.


It would also be reasonable to turn pig-out into an inline function, or 
a macro.  Let's try a macro:

---------------------------------------
module: capture

define macro awhile
  {awhile (?exp:expression) ?:body end} =>
    {for (?=it = ?exp then ?exp,
          while: ?=it)
       ?body
     end}
end macro;

define variable $fridge = as(<deque>, #("pizza", "steak", "yoghurt"));

define method poll(x)
  ~x.empty? & x.pop
end;

define method eat(x)
  format-out("Eating %s\n", x);
end;

define macro pig-out
  {pig-out(?_where:expression)}
    => {let where = ?_where;
        awhile(poll(where))
          eat(it)
        end}
end;

pig-out($fridge);
---------------------------------------
In Top level form.:
"capture.dylan", line 25, characters 15 through 16:
              eat(it)
                  ^^
  Error: Undefined variable: it
---------------------------------------

Oops!  ?= working the way Gabor likes it has injected "it" directly into 
the top level and it is not visible inside the expansion of pig-out.

I think it is surprising and wrong that perfectly reasonable code that 
uses awhile() suddenly stops working if you use it in the expansion of a 
macro.

This behaviour will prevent a *huge* number of the macros in _On Lisp_ 
from being converted into Dylan.

I believe that use of ?=it in awhile() should refer to a variable of 
that name in the textual environment in which awhile is mentioned.  It 
is the author of pig-out that is the "user" of awhile, NOT the person 
who wrote "pig-out($fridge)".

-- Bruce