Arc Forumnew | comments | leaders | submitlogin
[RFC] Macro Fix
17 points by nlavine 4282 days ago | 2 comments
There seems to be a slight limitation in the macro system in Arc. Basically, your ability to shadow things is forcing you to always shadow them, even if you don't want to, like in this code:

  ; copy-structure: return exactly its argument, but
  ; in new cons cells
  (mac copy-structure (lst) `(list ,@lst))
  ; (copy-structure (cons 'a 'b)) -> (a . b)
  ; tag-lists: take some code that makes lists, and
  ;  return one that makes tagged lists - but doesn't
  ; disturb literals
  (mac tag-lists (tag . body)
    `(let list (fn args (cons ,tag args)) ,@body))

  ; (tag-lists 'nums (list 1 2 3)) -> '(nums 1 2 3)
  ; (tag-lists 'nums '(1 2 3)) -> '(1 2 3)
  ; here's the problem:
  (tag-lists 'nums (copy-structure (1 2 3))) -> '(nums 1 2 3)
The problem is not that this happens (that's correct), but that there's no way to not make this happen. When you're defining copy-structure, you cannot guarantee that your definition of list is the one that will be used, and if you use copy-structure, you must be aware of the fact that the binding of list matters. This makes the implementation of copy-structure part of its definition (since you have to be aware of the implementation in order to use copy-structure correctly).

In some cases, this may be what you want; but it also might not. Luckily, there's a simple way to allow both possibilities. What you want to do is to allow someone to write copy-structure like this,

  ; note the lack of quotation of the symbol list
  (mac copy-structure (lst) (cons list lst))
so that the list function used is the one that copy-structure had. This is just like letting someone type in a function literal at the command line, instead of typing a symbol and having arc evaluate it (except it's much more useful for macros).

The only change is in the function literal? in ac.scm.

  (define (literal? x)
    (or (boolean? x)
        (char? x)
        (string? x)
        (number? x)
        (procedure? x) ; New line here!
        (eq? x '())))
This will allow people to write hygienic macros if they want to, while not requiring it in any way.

(edited for formatting)

2 points by icemaze 4282 days ago | link

I'd like to point out that this problem happens even when you don't redefine primitives or core functions. I read somewhere (maybe even one of PG's books, can't remember) that this is one of the reasons why Common Lisp is a LISP-2: because of unhygienic macros.

This is especially relevant when you use many libraries: it's not possible to remember all of their exported symbols and sooner or later you are going to shadow one of them. It only takes one macro call, then, to trigger a possibly very tricky bug.

This is made even worse by Arc's special syntax: if a variable contains a string or a hash instead of a unary function, this won't be detected until much later. So this problem seem to be more relevant to larger programs.

Is there a more elegant workaround? Does the devteam has something in mind?


2 points by jsg 4278 days ago | link

I agree. macro transformers would need to refer to the lexical context in which they were defined. strangely, a number of threads in this forum seem to indicate substantial resistance against implementing Arc macros any way other than as simple list manipulations. now granted that 1) straightforward list transformations are easy to understand, 2) there are limitations with _some_ macro systems that provide for lexical references to identifiers in the definition context, 3) inadvertently shadowing identifiers appearing in macroexpansions is unlikely in _other_ Lisps thanks to multiple namespaces, package systems and restrictions on shadowing certain identifiers, and 4) it is a lot more difficult to correctly implement a macro system that solves this problem. however, let's all agree that a hypothetical macro system that _could_ solve this problem would be vastly preferable. arguing against this on the grounds that accidentally shadowing identifiers is a problem you're willing to deal with manually (in a single-namespace Lisp with no module system and no shadowing restrictions) is analogous to arguing in favor of dynamic scoping over lexical scoping as a sensible default.