Arc Forumnew | comments | leaders | submitlogin
Referential transparency
1 point by xrchz 5836 days ago | 16 comments
so how would you write something like this in arc? (the following is in scheme)

  (define pusher cons)
  (define-syntax push
    (syntax-rules ()
      ((_ e ls) (pusher e ls))))
  (let ((pusher (lambda (x ls) (append ls (list x)))))
    (display (push 'a '(b c d)) (newline)
    (display (pusher 'd '(a b c)) (newline))
I want to get (a b c d) back twice. Note that the push macro may be in a library where I don't know that it's implemented in terms of pusher instead of cons. Not also that if push had been a function instead of a macro, this example would have worked; if push were in a library I might not even know whether it's a closure or syntax.

(These are justifiable names - for the macro, the person was thinking "pusher = function that does the pushing". In the let expression maybe they were thinking "pusher = PUSH Extending on the Right" (and have pushel somewhere too). If you don't buy that I'm sure you can come up with another example.)



1 point by fallintothis 5835 days ago | link

Not sure if this is exactly like the bug noted in the top comments of arc.arc, but it seems to me that perhaps it could be boiled down to the problem of inserting literal objects into macroexpansions:

  arc> (= pusher cons)
  #<primitive:cons>
  arc> (pusher 'a 'b)
  (a . b)
  arc> (mac my-push (e ls)
         (let pusher-alias pusher
           `(,pusher-alias ,e ,ls)))
  #3(tagged mac #<procedure>)
  arc> (let pusher (fn (x ls) (+ ls (list x)))
         (prn (my-push 'a '(b c d)))
         (prn (pusher 'd '(a b c))))
  Error: "Bad object in expression #<primitive:cons>"
  arc> (macex '(my-push 'a '(b c d)))
  (#<primitive:cons> (quote a) (quote (b c d)))
  arc> (mac my-push (e ls)
         (let pusher-alias pusher
           `(list ',pusher-alias ,e ,ls)))
  *** redefining my-push
  #3(tagged mac #<procedure>)
  arc> (let pusher (fn (x ls) (+ ls (list x)))
         (prn (my-push 'a '(b c d)))
         (prn (pusher 'd '(a b c))))
  (#<primitive:cons> a (b c d))
  (a b c d)
As you note, this solution then turns into combing through and aliasing every free variable -- doing so automatically would then be the makings for some hygiene, ostensibly. This is an area of contention, as I'm sure you're well aware. It still crops up fairly frequently, generally from someone vying for such a system. But as it stands, Arc doesn't seem to be going for hygienic macros (except perhaps as a library).

-----

1 point by xrchz 5834 days ago | link

is it possible for a macro library to provide hygienic macros? I suppose you could port the "portable syntax case" implementation from scheme to arc... but I don't know how much help, if any, you'll need from the expander

-----

1 point by rincewind 5834 days ago | link

how do you know that a variable is free at macro expansion time?

-----

1 point by stefano 5834 days ago | link

You would need help from the compiler. Basically a macro would be called with a list of bound variables as an extra argument. For a compiler, this would be quite easy. Now a macro would look like:

  (mac my-mac (non-free-var-list my-arg1 ...)
    ...)
instead of:

  (mac my-mac (my-arg1 ...)
    ...)

-----

2 points by almkglor 5833 days ago | link

Why not just mod 'mac to something like (docstring extraction and name redefinition elided):

  (mac mac (name args . body)
    `(set ,name
       (annotate 'mac
         (fn ,(cons 'expansion-context args)
           ,@body))))
Then insert stuff in Scheme-side 'ac to use (apply macro the-free-list-or-whatever (cdr ex)) instead of (apply macro (cdr ex))

That way macros that need it can get it, while macros which don't need it can ignore it.

We can potentially define 'expansion-context as a table or function, and potentially we could allow an implementation to have expansion-context!line, say provide line number etc. information so that the macro can give decent error messages. Of course if we have a dorky backend (like mzscheme) then we can just have it return expansion-context!line as nil, because it can't somehow extract line numbers, but for implementations that can, then good.

edit: Oh and ye: the 'ac compiler on scheme-side really does keep a list of bound variables ^^

-----

1 point by rincewind 5834 days ago | link

This is a solution, but I consider this cheating, because it is nearly as tedious as manually inserting symeval around global functions.

w/var:do in my mexpr.arc is an example of a macro/dsl which can not take a free-var list as 1st arg (it could be changed, but that would make it useless)

and what about list in

  (let list (list 1 2 3) list)

-----

2 points by almkglor 5835 days ago | link

Well, I did propose a hackish extension, 'symeval, some time back. I should probably push the extension onto Anarki. T.T

  (= pusher cons)
  ; push is defined in arc.arc
  (mac my-push (e l)
    `(symeval!pusher ,e ,l))
  (let pusher (fn (x ls) (+ ls (list x)))
    (prn (my-push 'a '(b c d)))
    (prn (pusher 'd '(a b c))))
Basically symeval is a non-overrideable special form (on the same level as 'set, 'if, etc.)

http://snapvm.blogspot.com/2008/07/symeval.html

-----

1 point by xrchz 5835 days ago | link

I think 'symeval is a possible solution to this problem, and I'm glad somebody thought of it before me. But the question is why you wouldn't use 'symeval on every free variable in your macro template, just in case some newbie decides to let bind one of your "global but internal" names. If you did use 'symeval everywhere (or automatically) you would have something closer (but still a poor approximation) to scheme's hygienic macro system. I think the automatic solution to be found there is more interesting.

-----

2 points by rincewind 5835 days ago | link

I tried that. It doesn't work without knowing which variables are local and arc currently has no way of finding out, what the binding forms are, because all macros are unhygienic.

example:

  (let list '(foo bar) (do (prn list)))
    |    |   unbound    |    |    |
   mac   |             mac   | function 
      function            function

  =>(let (global list) '(foo bar) (do ((global prn) (global list))))
We know that let binds its first argument and aif binds self, but since macros are turing-complete, this cannot be automatically inferred.

-----

1 point by almkglor 5833 days ago | link

expand any macros in sub-expressions, then look at them. That's the advantage of avoiding "first-class macros", whatever their imagined advantages might be. ^^

-----

1 point by xrchz 5835 days ago | link

is it really true that 'set and 'if (and 'symeval...) are completely non-overrideable in arc? is that a convention or something enforced?

-----

1 point by almkglor 5833 days ago | link

It's enforced. For example, you can assign a macro to 'if, but it'll never be used: 'eval will always interpret 'if in its sense, and will never look at your macro.

-----

2 points by drcode 5835 days ago | link

You're asking a complicated question with a complicated example and don't have matching parentheses.

You can't expect us to answer this without supplying us with a proper example.

-----

2 points by xrchz 5835 days ago | link

I'm really sorry my parentheses were mismatching. Here is a correct example.

  (define pusher cons)
  (define-syntax push
    (syntax-rules ()
      ((_ e ls) (pusher e ls))))
  (let ((pusher (lambda (x ls) (append ls (list x)))))
    (display (push 'a '(b c d))) (newline)
    (display (pusher 'd '(a b c))) (newline))

-----

1 point by almkglor 5835 days ago | link

Well, I think it's an example of the problem I pointed out here: http://snapvm.blogspot.com/2008/07/symeval.html

-----

1 point by xrchz 5835 days ago | link

I think you're right.

-----