I presume arc3.1 is only faster if you use plt4, which is attributable to plt4 being faster than mzscheme 372 thanks to improvements to the language implementation. The speed boost obviously doesn't come from making conses immutable, since Arc still has mutable conses.
If you're asking why immutable conses could speed up a programming language in general, well, a compiler can make more powerful optimizations if it's allowed to assume that lists won't mutate mid-flight. Obviously a specific program that used mutability of conses would need to be rewritten, and might end up faster or slower; but code that doesn't take advantage of the mutability of conses (of which there is a lot, Scheme having the functional heritage it does) can be speeded up.
Of course, given that Arc's mutable conses are accomplished by using pointer hacking to mutate plt4's supposedly immutable conses, one would hope the plt4 compiler doesn't take real advantage of the immutability of their conses, otherwise it might make assumptions that Arc will break, leading to difficult-to-detect bugs.
; Eli's code to modify mzscheme-4's immutable pairs.
;; to avoid a malloc on every call, reuse a single pointer, but make
;; it thread-local to avoid races
(define ptr (make-thread-cell #f))
(define (get-ptr)
(or (thread-cell-ref ptr)
(let ([p (malloc _scheme 1)]) (thread-cell-set! ptr p) p)))
;; set a pointer to the cons cell, then dereference it as a pointer,
;; and bang the new value in the given offset
(define (set-ca/dr! offset who p x)
(if (pair? p)
(let ([p* (get-ptr)])
(ptr-set! p* _scheme p)
(ptr-set! (ptr-ref p* _pointer 0) _scheme offset x))
(raise-type-error who "pair" p)))
(define (n-set-car! p x) (set-ca/dr! 1 'set-car! p x))
(define (n-set-cdr! p x) (set-ca/dr! 2 'set-cdr! p x))
Really. (This is an excerpt from arc3.1's ac.scm.)
It seems that plt4's immutable conses have been worked around via low-level pointer hacking. I can only hope this doesn't introduce subtle bugs, but given that Eli Barzilay seems to work on plt scheme, hopefully this is not the case.
I've pushed the changes to anarki's "official" branch.
This isn't a bug per se. It just prints the dot before it evaluates your code. So the first iteration, it prints 1; the second, 2; the third, 3; the fourth, it prints a dot and THEN prints 4.
Another bug: 'indent-pairs can't handle being presented with a list containing only one element. For an example in the wild, see the source of 'rand-choice:
arc> src.rand-choice
(from "arc.arc")
(mac rand-choice exprs
`(case (rand ,(len exprs))
Error: "car: expects argument of type <pair>; given 1"
More succinctly:
arc> (ppr '(case foo ,@(bar baz quux xyzzy plugh very long)))
(case foo
Error: "car: expects argument of type <pair>; given 1"
The error lies in 'indent-pairs:
(def indent-pairs (xs (o col 0))
(let l (apply max (map len:tostring:print:car (keep [cdr _] pair.xs)))
(on x pair.xs
(if (~is index 0)
(do (prn)
(sp col)))
(let str (tostring:print car.x)
(if cdr.x
(do pr.str
(sp:- l len.str -1)
(ppr cadr.x (+ col 1 l) t))
(do (sp (+ l 1)) ;offending expression
pr.str))))))
If 'xs is a list with only one element, then (pair xs) returns a list whose sole element is itself a list of one element. 'l is then the result of applying 'max to an empty list, which is nil. (+ l 1) then becomes (+ nil 1), which errors. One simple solution is to add a preceding 0 to the application of max.
--- ppr-old.arc 2009-07-28 21:10:49.000000000 +0200
+++ ppr.arc 2009-07-28 21:10:52.000000000 +0200
@@ -54 +54 @@
- (let l (apply max (map len:tostring:print:car (keep [cdr _] pair.xs)))
+ (let l (apply max 0 (map len:tostring:print:car (keep [cdr _] pair.xs)))
The reason why the first example works is that `(foo (,a . ,b)) is syntax for (quasiquote (foo ((unquote a) unquote b))), which doesn't contain dotted lists.
I'll try my hand at fixing this when I have the time.
This does weird things to quasiquote in medial position (see below), but IMO
doing that is suspect, and it fixes both the "can't use dotted lists in
quasiquoted expressions" bug and the "dotted unquote only works sometimes" bug:
For those interested, more detail follows on the corner cases of quasiquotation
in the underlying mzscheme. As it turns out, mzscheme (possibly schemes in
general) has reallyweird quasiquoting rules. Consider the following:
; we begin with normal examples
> (define x 'X)
> (define X 'value)
> `(a ,x)
(a X)
> `(a `(b ,,x))
(a (quasiquote (b (unquote X))))
; quasiquoting in dotted position has interesting results:
> `(a . `(b ,x))
(a quasiquote (b (unquote x)))
> `(a . `(b ,,x))
(a quasiquote (b (unquote X)))
> (eval `(list . `(b ,,x)))
quasiquote: bad syntax in: quasiquote
; unquoting in terminal position results in splicing, as expected
> '`(a . ,x)
(quasiquote (a unquote x))
> `(a . ,x)
(a . X)
> `(a . ,(list x))
(a X)
; unquotes in medial positions error, which is good:
> `(a unquote x X)
stdin::1874: unquote: expects exactly one expression at: (#<syntax::1879> #<syntax::1887> #<syntax::1889>) in: (quasiquote (a unquote x X))
; however, the same is not true of quasiquotes in medial position.
> `(a quasiquote b c)
(a quasiquote b c)
; in fact, quasiquotes in medial position require increased unquoting of
; subsequent elements - quasiquoting in dotted position, above, is a special
; case of this
> `(a quasiquote ,x ,x)
(a quasiquote (unquote x) (unquote x))
> `(a quasiquote ,,x)
(a quasiquote (unquote X))
; however, if the list following quasiquote is dotted, this does not happen...
> `(a quasiquote ,x . y)
(a quasiquote X . y)
; ... UNLESS the dot comes immediately after the quasiquote
> `(a quasiquote . ,,x)
(a quasiquote unquote X)
Since arc quasiquotation compiles down into scheme quasiquotation, I'm unsure of
the best way to handle all this at the arc level. What should all of these
corner cases mean in arc? Moreover, consider that all uses of medial quasiquote
(eg '(list quasiquote 2)) result in syntax errors when evaluated in scheme, but
in arc, evaluating eg '(list quasiquote 2) is not a syntax error, and will
depend upon the value of 'quasiquote; in fact, the same is true of most special
forms:
The answer is that arc quasiquotation gets compiles down to scheme quasiquotation, and mzscheme's quasiquote handler understands (... unquote bar) to be equivalent to (... (unquote-splicing bar)). Since the arc compiler doesn't understand unquotes of this style, you'll only get the correct results when the expression unquoted is the same in arc and scheme - ie: it's either a literal, or it's a local variable. For example:
; this works because the local 'x in arc is compiled to 'x in scheme as well
arc> (let x 0 `(a . ,x))
(a . 0)
; this works because '+ in scheme adds numbers just as in arc
arc> (let x 0 `(a . ,(+ x 1)))
(a . 1)
; the following examples do not work
arc> (let x '(b) `(a . ,(join x '(c))))
Error: "reference to undefined identifier: join"
arc> (let x '(b) `(a . ,(+ x '(c))))
Error: "+: expects type <number> as 1st argument, given: (b . nil); other arguments were: (c)"
arc> (= x '(b))
(b)
arc> `(a . ,x)
Error: "reference to undefined identifier: x"
It's funny how a surface bug turns out to lead to something deeper in this way. I feel like Alice down the rabbit hole.
I've never seen a convincing use case for currying macros; not sure why you bother with that. I find your use of 'args-key to enable reverse currying interesting - one wonders whether there are other possible uses. Personally I just use the following:
(def curry (f . xs) (fn ys (apply f (join xs ys))))
(def flip (f) (fn (x y . z) (apply f y x z))
As regards reversed currying and other more complex partial applications, I find the extended version of the [] syntax that I've cooked up for anarki's arc3.master branch to be quite nice - any symbol prefixed with an underscore is taken to be an argument to the function, and arguments are ordered alphabetically (with the special symbol '__ standing for the rest argument). So for example:
My motivation for currying macros came from the recent "afn with" discussion. I had a w/afn macro which looked like a manually-curried function, so I figured it wouldn't hurt to add it to the curry fn:
(mac w/afn args
`(w/rfn self ,@args))
vs.
(= w/afn (>_ w/rfn 'self))
Your [] syntax definitely looks useful, but I use (fn ((x y))) more than (fn (x y)).
I have (finally) merged and pushed these changes to anarki's arc3.* branches and hacktags.
I also have the beginning of a script for managing updating all these hacktags in parallel, but the sheer complexity of it is making me rethink the model of separating hacks from one another.
What other choice is there if I want to maintain independence of hacks? If I just use plain diffs, how do I update my hacks when a new arc3.tar comes out and breaks them? Manually? That's even more of a pain.
I have some ideas that appear to be promising, but no solution to offer you yet. For example, I look at one of my patches and say, "why do I need a patch? Why isn't Arc hackable enough to let me implement this by just loading some code?" and, if I can, see if I can make Arc more hackable instead. And then, if it works, my patches become much smaller, just extending Arc to become more hackable instead of implementing my whole hack, small enough so that often the patch will still apply in new versions of Arc. Now when a new version of Arc comes out I don't always have to come up with a new patch, instead what I'm doing is simply testing my patches to see if they still apply.
Infix + triggers ssyntax for ac-andf - even+odd expands to (andf even odd), for example. However, the expander doesn't handle +'s in non-medial position very well - it just cuts them out:
arc> (ssexpand '++foo)
foo
Since medial + is checked before medial dot in ssyntax expansion, this leads '++.n to expand to '.n and thence to (get n).
Albeit it does change the semantics of some "correct" uses of ssyntax slightly (though not in a way I'd expect anyone to rely on); for example, 'foo+bar.baz expands to '(andf foo (bar baz)) under arc3, but expands to '((andf foo bar) baz) with this patch applied.