Is that because Scheme doesn't allow you to use a data item as the first item in a form? Or does it have some other mechanism currently lacking in Arc to help with this problem?
The reason (tab 'foo) doesn't work in arc is that ac (in ac.scm) effectively has a special list of macros, and when it compiles (tab 'foo), it looks up tab in the special list. What is really should do in a lisp-1 is look up tab in the same list that all the other variables are in, which is the list that (let tab (table) ...) will modify. (Yes, I know I'm simplifying. No, it doesn't matter in this case, unless you can think of a reason that it does.)
Also, there's only one list like this, and it has global scope, so you can't have local macros or anonymous macros.
Ok, so if it worked that way, then (tab 'foo) would work, but you would have shadowed the tab macro, and thus lost the ability to use it, which doesn't sound like a problem if you didn't know tab was a macro to begin with.
Anyone know if this is how Arc will work, or if the current behavior is by intent?
the problem that other people have stated is that because arc's macros are unhygienic, allowing local variables to shadow macros causes some pretty serious problems.
It does seem like you're going to want to do a lot of macro composition where you only compose over the body argument. Is it possible to make this work? The most obvious way for me is to use the single-argument function syntax, but somehow modified for macros, like this:
([let x 3 _]:pm:body)
This is just a really quick thought. Any comments?
That is certainly the arc way to do it, but there's one thing I'm not sure about: do you want mac:let or let:mac? Or, in fact, would either one work? The problem I'm trying to get at is what happens to let's three arguments (symbol, value, body) when you compose mac with it. It seems like you really want mac to apply to only the value argument, which is not a straight composition.
I almost hate to get into this, but it seems to me that the real solution is first-class macros:
(let my-macro (mac (x) `(prn ,x))
...)
I believe pg said that one of the things he gave up in the current implementation that he'd really like to have is first-class macros, and it may be that we can't get them in mzscheme without unacceptable performance loss, so perhaps maclet is the temporary solution, but this seems like the long-term way to go.
arc> (let m (mc (x) `',x) (m 'not-a-declared-symbol))
(quote not-a-declared-symbol)
. However, here I'm not sure why. So after working through this, I'm not actually sure what these results signify. But here they are. Make of them what you will.
I'm not exactly sure if this is what you want. I believe there is a difference between macros that are only expanded once (in lazy compilation), and macros that are called each time the macro-expression is encountered. Though the latter case would only make sense if the macro exhibits side-effects, or depends on side-effects, or if the argument list to the macro may change. Not sure whether there is any benefit in that.
first-class macros would obviously solve this problem completely, that same way scheme eliminates the need for CL's flet and labels. it would be nice if we could just assign anonymous macros to bindings, but we can't do that yet.
the problem is that the composition just doesn't really make sense in this context, as you said. it doesn't translate the way composition should. in my opinion, maclet is deserving of it's own form.
On the contrary, the question of how a particular implementation represents with arc lists has nothing to do with what arc lists are.
In fact, you could make ac.scm more efficient by doing a (define nil '()), eliminating all the denil stuff, and then changing the print routines, if you cared to.
Is ac-niltree/ac-denil a constant overhead, or does it make some O(1) operations into O(n) operations? Hopefully the nil/denil traverses the whole list only when you're already traversing the whole list. But I haven't been able to convince myself that's always the case. This is on my list of things to investigate, but has anyone already figured this out?
I'm not completely sure, but I think this is a compile-time overhead. So when you type directly into the toplevel, its a O(n) overhead, but when you execute a function defined in arc, executing the function itself should be less additional overhead because it was compiled to a scheme function in the original def.
; translate fn directly into a lambda if it has ordinary
; parameters, otherwise use a rest parameter and parse it.
(define (ac-fn args body env)
; ...
Have to watch out for corner cases - Arc's usage of 'nil as the list terminator is exposed in several places within the language itself. For example `(is (coerce "nil" 'sym) (cdr nil))` is true under the existing implementation, yet would be false if you just (defined nil '()). You'd need to patch `is`, `coerce`, and possibly a bunch of other primitives to preserve the existing behavior.
Which exposes the problem of "the code is the spec". If there was a prose spec, pg could have just said that such stuff was undefined and we wouldn't care that such things break.
If stuff doesn't work right, having the spec say it's supposed to not work right doesn't help at all.
Prose specs came into being to help multiple implementations interoperate (somewhat) when implemented in low level languages. I don't see the need when you have just one pretty concise and declarative implementation.
The question of how a particular implementation represents with arc lists has nothing to do with what arc lists are.
The code is the spec.
The fact remains that in arc when you type '(a b c) you get '(a . (b . (c . nil))) and in scheme you get '(a . (b . (c . ()))).
You could make ac.scm more efficient by doing a (define nil '())
There is a difference between nil being the empty list and nil evaluating to the empty list.
One problem with (define nil '()) is that then 'nil returns the symbol nil rather than the empty list ().
I am not convinced that (define nil '()) would greatly increase efficiency, because most of that overhead is incurred in the initial read, and not at run time. It takes billions of times longer for the user to type a statement into arc than it takes arc to denil the input. And after that, nil or () isn't an issue because arc functions are compiled to scheme functions anyways.
I'm fairly sure that "the code is the spec" applies to arc.arc, not to ac.scm. That is, anything that can interpret arc.arc properly is a conforming Arc implementation; ac.scm is just one of them. And since nil is (), then in Arc, your two lists are the same but with different formatting. Observe:
arc> (is nil '())
t
arc> (is nil ())
t
arc> (iso '(a b c) '(a . (b . (c . nil))))
t
arc> (iso '(a b c) '(a . (b . (c . ()))))
t
arc> (iso '(a . (b . (c . nil))) '(a . (b . (c . ()))))
t
Please note that the behavior of is is defined by ac.scm, not arc.arc. (And iso uses is to test for equality.)
The equality and interchangability of nil and () is just because there is a line in the definition of is that specifically says that false values are equal.
So even if you put #f in the terminating position, your lists will still be iso.
arc> (iso '(a b c) '(a b c . #f))
t
Are you saying that arc lists are #f terminated just because it doesn't break arc to put #f in random lists? Even if arc lists can be terminated by nil, (), and #f, it doesn't mean they necessarily are. Any given list (ignoring nested lists) can only have one termination object. I think it is reasonable to say that that list is terminated by that object. I do not think it is reasonable to say that list is terminated by another object, even if it could be, because the actual object at the end of the list is not that other object. And because the current implementation (the only official implementation) uses nil, I think it reasonable to say that arc lists are terminated by nil. Note the use of the present tense. If that changes at some future time, I my statement will obviously not apply.
In short, given the current implementation, we have (at least) six different ways to write nil: nil, 'nil, (), '(), #f, and '#f. These six things are all considered identical within Arc. The same is true, for instance, of 'a and (quote a), which are the same thing entered differently. You can check that they are the same with is; I won't put all those examples into this post.
I have never said that Arc lists aren't nil terminated--clearly, they are. What I'm saying is that Arc lists are nil terminated for any representation of nil--whether that representation is nil from Common Lisp, '() from Scheme, or #f from a leftover artifact of using the mzscheme reader.
Hygienic macros do not require pattern-matching, as proven by the example of MIT scheme. Basically, macros in their system take three arguments - an s-expression, the environment the s-expression is being evaluated in, and the environment the macro is written in - and return syntax, which is an s-expression where you've chosen explicitly for each variable what environment it should be evaluated in. (More or less. If you're interested, it's called syntactic closures and you can probably find stuff about it online, or I can just write more here if people want.) I don't remember any way to insert a free identifier, but it might be there.
By the way, arc can already do half of that if you put (procedure? x) into literal.scm. The harder part is making variable references to things that aren't procedures, and I'm not quite sure how you'd do that (except the really annoying way, by wrapping everything in a procedure application).
SRFI-72 is another proof, as someone posted below, and might be better than syntactic closures (I don't know).
Keyword arguments almost work with the current optional argument syntax, except for reordering:
(def cut (seq (o ('start start)) (o ('end end)))
...)
And you call it like this:
(cut seq (start x) (end y))
(destructuring with literal symbols - don't know if that's supported yet, but it should be.)
The only problem seems to be that you can't reorder them. What if you make the rule that you can have more than one name after the dot in an argument list, but they're unordered?
Hmm. On further investigation, this solves one of the two problems. The other one is that '() and #f are still different Scheme objects, so certain functions would have to stay. But I think this could still tighten up the Scheme code somewhat.
You're right, this wouldn't work with the current ac.scm. However, if we stop treating the symbol 'nil specially and just let it be a normal variable lookup, then I think everything will work.
The only hard bit, actually, will be making it output things using 'nil instead of '(), but making a fix like that, that's isolated in one small part of the program, seems like a small price to pay for making the main arc compiler sleeker and faster (oh, and the generated code, too).
Hmm. A quick check in the arc interpreter reveals that (car nil) and (cdr nil) are both nil, so you're right about that.
However, there could be another reason this is working. In your particular example, the full expanded version of the function call is
(foo . (nil . nil)),
or in other words,
(cons foo
(cons nil
nil)).
So it really should be destructuring that way anyway.
Now, having said that, can anyone give a good reason that (car nil) and (cdr nil) are nil? It seems a little strange to me, although I can see that it helps destructuring.