There was a post a while ago about a form of let that managed to mix both multiple assignments and implicit progn behavior, although if I recall correctly it had problems with destructuring binds.
The post in question was actually about conciseness as a metric, and the example used in his argument was exaggerated to make his point, but the form he created (the modified let) might actually be useful (although the last stage of his example was a little scary... maybe we shouldn't take it all the way).
I don't think mixing the two is good. I think the lack of implicit progn would be a Good Thing. The explicit progn makes non-functional code stand out, and it also makes the language a bit more uniform if no primitives have implicit progn. All other things being equal uniformity is good because it reduces surprises.
Unless there are a lot of people who feel like the implicit progn is an important part of "let" I'd prefer to not have it.
I know this doesn't address many of the issues with a module system in arc... but there is currently an import.arc file in the arc-wiki that has a few interesting points.
In the current import.arc, when you import a module, the name of the module gets set to a macro which allows you to access functions in the module via composition. Thus you get to use : rather than another arbitrarily chosen character. I just thought that was kind of cool.
Maybe there is a problem with just simple composition that doesn't scale when applied to a full blown module system. But if you could incorporate this into any ideas currently going around, I think it would clean up the syntax for accessing internals of a module at the very least.
From the arc side of things it may look like () and nil are the same, but on the scheme side of things, nil is a symbol, not at all related to the empty list ().
And as a matter of fact, arc lists are indeed terminated with the symbol nil.
> mzscheme -m -f as.scm
Use (quit) to quit, (tl) to return here after an interrupt.
arc> (cdr '(1))
nil
> mzscheme
Welcome to MzScheme version 352, Copyright (c) 2004-2006 PLT Scheme Inc.
> (cdr '(1))
()
the second arc example '(1 . ()) evaluates to (1) but still results in a nil terminated list
arc> (cdr '(1 . ()))
nil
The fact that nil and () happen to be equivalent in arc has nothing to do with whether lists are nil or () terminated.
And I could have sworn I had gotten an error once that showed that an arc list had gotten into scheme somehow, but I can't remember exactly what I did to cause the error right now. I think it was something like:
Error: some function expected a proper list but got '(3 + 4 . nil) instead.
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.
I've been working on a version of infix syntax written in arc that relies only on the ability to switch the first and second places in a function call when the functional position contains a number. This means that the solution relies on minimal changes to ac.scm, and can be loaded either automatically or optionally into the running arc toplevel.
I also added setter forms so the user can now define precedence of an operator as such:
(= (precedence %) 2)
or
(= (precedence %) /)
And I also experimented with pg's other suggestion for literals being constant functions when in functional position. I found you can have both that and switching arguments at the same time, for example:
[3] ; always evaluates to 3
(3 + 4) ; same as (+ 3 4)
(3 + 4 * 5 - 6) ; when using my infix library
This version being written directly in arc, it is probably slower than the scheme version. But considering it is much less intrusive to ac.scm, I will probably push this version to the arc-wiki.
This means that infix.arc now gets loaded by default when arc starts. Since infix.arc redefines the existing +-/* operators, if you don't want this to happen when arc loads, you can remove it from the list in libs.arc, then load it or not at your leisure.
In fact, both were needed to optimize things. Mzscheme's doc says explicitly (though I can't remeber where exactly) that standalones are a little faster than interpreted code (there are a few more optimisations they can perform during compilation I guess). Running arc1.scm through mzscheme -f is a little slower.
There is an initial overhead for byte-compiling the code
and jitting it -- but that's not something that you'd be
able to measure for such a small piece of code. Once
that's done, it's the same code -- mzscheme (since a good
while ago) on intel and ppc does not interpret code. Ever.
Even on solaris, where the jit is disabled, it's
"interpreting" byte-compiled code, so it is not an
interpreter in any case.
Thanks, although it seems to regard mostly macros.
PG commented "The single biggest compromise I had to make because of MzScheme was not being able to put objects like functions and hashtables into macroexpansions." Not exactly sure what he meant by that, or if it had anything to do with the evaling functions directly.
I guess what I am really wondering is if there is an intrinsic reason why evaling a function directly can't work, or if support for it just doesn't happen to be in Arc currently because PG never needed it.
[Note: Like functions, hash tables can't be printed out in a way
that can be read back in. We hope to fix that though.]
The problem that I see with macros+hashtables in macroexpansion, as well as with printing/reading-back-in functions+macros+hashtables, is that currently an expression is compiled, before it is evaled, and loses its original uncompiled form. That's why macros are currently not truly first-class.
If used with eval or in macroexpansions this only causes a problem with macros and hashtables, not with functions or hashtables used as functions. Like nlavine pointed out, it only takes an additional line of code in "ac" to make your example work.
Although literals in functional position may be valuable real estate, I think that infix math is valuable enough to justify using it (at least until we think of something more important).
The only other thing that was suggested in the comment in ar-apply was that literals in functional position might be constant functions. I think infix syntax gives the programmer far more expressive power than being able to denote constant functions.
I took a shot at writing an indentation based syntax a while ago. You don't technically need the colons, although that makes it difficult to introduce new parens mid-line.
I like the idea of indentation based syntax, but the colon solution looks like only half a solution. You only remove half the delimiting characters, and the ones you remove are the ones that are the easiest to type when you have a good editor. (In fact, some editors even add them for you.) Considering that I prefer parens to colons as delimiters, I would currently prefer to stay with s-expressions.
And as much as I might like indentation based expressions, kennytilton has a good point about editing code. In indentation based syntaxes, you have to redo the indentation manually (or rely on an editor to guess), whereas in traditional lisp you can just hit the auto indent key (since indentation can be determined programmatically).
The colon proposal attempts to be orthogonal to traditional
s-expression notation (every s-expression in traditional
notation keeps reading the same as always, no matter how it
is formatted).
> I like the idea of indentation based syntax,
but the colon solution looks like only half
a solution
Well, if we want to read s-exprs, _somehow_ the
programmer must hint the reader about where the
parens go.
I don't want the reader to guess what I mean, or
juggle with whitespaces.
I think the colon notation is more visually appealing
than "just parens", and doesn't hurt programmers who
don't want to use it.
Since, as I've pointed out, the editor can handle the parens for you completely and unabiguously, even without any special commands, you could simply turn-off, parens that exist in addition to indentation, make them invisible.
If you then additionally make the editor display the opening parenthesis as a colon then, voila, you have the visually pleasing colon syntax.
In such a mode you'd always have to be indentation perfect, but just as with colon syntax, you can simply switch to a more traditional editing mode.
Very nice. Both your suggested commands worked for me (on Windows XP).
I like the GUI especially (it actually solves a bug I found with mzscheme that was stopping me from pasting code directly into the command line). Although I find it somewhat strange that I have to click quit twice in order to actually close the program.