I don't know. If it comes up often enough, I think I'd rather have a special (fncall x 'y) ssyntax. Maybe x!y could expand to (fncall x 'y) and x.`y could expand to (x 'y).
I wrestled with this disambiguation problem for some time and finally settled (for now ;) on a simple inference system based on the most common use cases. The algorithm is:
1. If the form has a single quoted arg, as in (x 'y), it's compiled to x['y']. This allows object access chains like document!body!innerHTML to be compiled correctly by default.
2. If the form has 0 or 2+ args, or 1 arg that isn't quoted, then it's considered a function call:
(x) => x()
(x y) => x(y)
(x y z) => x(y,z)
I'm still looking into the least kludgy way to pass a single quoted arg to a function. Here are some options:
(x "y")
(x `y) ; quasiquote isn't currently used for anything else
(x 'y nil) ; the function can just ignore the nil arg
(fncall x 'y)
I finally pushed this to Lathe. It's in the new arc/orc/ folder as two files, orc.orc and oiter.arc. The core is orc.arc, and oiter.arc is just a set of standard iteration utilities like 'oeach and 'opos which can be extended to support new datatypes.
The main feature of orc.arc is the 'ontype definition form, which makes it easy to define rules that dispatch on the type of the first argument. These rules are just like any other rules (as demonstrated in Lathe's arc/examples/multirule-demo.arc), but orc.arc also installs a preference rule that automatically prioritizes 'ontype rules based on an inheritance table.
It was easy to define 'ontype, so I think it should be easy enough to define variants of 'ontype that handle multiple dispatch or dispatching on things other than type (like value [0! = 1], dimension [max { 2 } = 2], or number of arguments [atan( 3, 4 ) = atan( 3/4 )]). If they all boil down to the same kinds of rules, it should also be possible to use multiple styles of dispatch for the same method, resolving any ambiguities with explicit preference rules. So even though 'ontype itself may be limited to single dispatch and dispatching on type, it's part of a system that isn't.
Still, I'm not particularly sure orc.arc is that helpful, 'cause I don't even know what I'd use it for. I think I'll only discover its shortcomings and its best applications once I try using it to help port some of my Groovy code to Arc.
Yeah, I'm not trying to say you shouldn't use it for production use :)
They're opposing perspectives. As a user of arc I'd throw it into production[1]. At the same time, from PG's perspective I'd want to be conservative about calling it production ready.
I suspect arc will never go out of 'alpha' no matter how mature it gets, just because PG and RTM will not enjoy having to provide support, or having to maintain compatibility.
[1] With some caveats: treat it as a white box, be prepared to hack on its innards, be prepared to dive into scheme and the FFI. And if you're saving state in flat files, be prepared for pain when going from 1 servers to 2.
Could you talk about your decision to use it for Readwarp then? If Arc's not really ready for production use, might it still be a good choice for a certain minority of developers?
Surprisingly enough, the only other change required is to replace else in case expressions to (else). Now you can convert ac.scm into a simple file that can be load'ed.
I started out pruning ac.scm from the bottom up, stripping out everything but the core compiler - and the culprit ended up exactly at the top.
(require (lib "foreign.ss"))
I assume the FFI does weird things with underscored variables that need to be sandboxed into a module before arc can take over such variables.
Update: I've uploaded a minimal arc implementation at http://gist.github.com/488376. May be easier to read than the canonical version. And I think it's portable R5RS now that it's not asking for the legacy mzscheme language.
For some reason, now I don't notice any issues with the "arc>" prompt in "racket" either. And I don't think I'm doing anything differently than I was before. ...I am forced to conclude that, when entering things into the REPL, I held down the return key long enough that it accepted an extra (blank) line of input. This explains the behavior exactly. Strange that I should have done this several times in a row... and how embarrassing. Oh well. At least now I can give racket a clean bill of health.
That is a known issue with Windows. (I'm guessing it's the reason arc3 is still the "official" version on the install page.) Simple workaround[1]: Find the line that says:
In fact arc3.1 even works on Racket, the new PLT Scheme. Only thing is that the command-line "racket" prints a newline after the "arc>" prompts, for some reason. But you can open as.scm with the editor DrRacket (as you could with DrScheme), set the language to be "Pretty Big", and hit Run; it will work.
Ah, I see. Yes, at the moment this compiler isn't very good at generating minimal JavaScript, since it's so faithful to arc.arc's macro definitions. A lot of the later work might involve optimizing it to produce smaller, more efficient JS.
Of course, you can still use (((((a!b 4) 'c) 'd) 'e) 'f) to generate a.b(4).c.d.e.f. [1]
> mzscheme 372 does not compile for me
Did you know Arc 3.1 works on the latest MzScheme? [2]
---
[1] Actually, you might be further disappointed to know (((((a!b 4) 'c) 'd) 'e) 'f) is currently compiling to:
get here is a JS function not unlike rocketnia's ref [3]. Its purpose is to disambiguate the Arc form (x y), which may compile to x(y), x[y] or (car (nthcdr y x)), depending on the type of x (function, array/object or cons, respectively).
No, no, I trust that your translation is correct. I was just disappointed that it would compile down to this much JS code since my example was design to model a.b(4).c.d.e.f.
I don't have a running Arc to check it on at the moment because mzscheme 372 does not compile for me (probably my gcc version is too new).
arc> (ssexpand 'a:.b)
(compose a .b)
arc> (ssexpand '.b)
(get b)
So, we need to recurse in the f argument anyway. At a certain point, it seems like the anonymous & higher-order functions add layers of indirection on what should just be a straightforward recursive definition.
I get really annoyed at that, though, when working with trees in Arc. There always seems to be some underlying pattern that's just different enough that I can't abstract it into a higher-order function.
It appears I've reinvented a worse version of your wheel. ^_^
Your ssexpand-all is superior and I'm using it now. I did try to refactor it, thinking there must be a function f (like my ssexpandif but more sophisticated) that satisfies
(treewise cons f expr)
while producing the same functionality, but I haven't been able to determine what that would be.
> The table-like syntax is nice, but it has the following problem. [...] Unless I'm missing something, there is no way to have ssyntax for the part after the first set of parentheses.
Yes, this is sometimes a problem for me too, or at least an annoyance. It's one of those things that's a bug or feature depending upon who you ask, though. [1] Whichever way you classify it, the root issue is with Arc, not the compiler, which just conforms to Arc's ssyntax rules.
Interesting formulation, but the inner parens' inclusion of value makes it look like value is another argument in the function call. It also might be too similar to dotted cons notation, e.g. '("foo" . value).
Hmm... my code originally looked a lot like that (except for naming and the implementation of 'unless-both). Then I edited my post quite a bit because I thought there was a problem with the (and (unless-both ...) (congruent-sigs ...)) expression. (I can't remember what it was now, and I think I was mistaken.)
So I added the 'either-way parameter, and then I moved the logic into a function to make the evaluation order more to my liking--no calling the test on 'x before 'y is evaluated--and it got to the complicated state it's in now. In fact, I see some bugs now; the place where it says "do.test.y (aand (ifneither)" should be "do.test.y nil (aand (do.ifneither)".
For whatever it's worth, here's 'ibona again, with your much better name, and without 'eitherway. The only thing that's really different from your version is the argument evaluation timing.
(mac unless-both (test x y ifneither)
`(fn-unless-both ,test ,x ,y (fn () ,ifneither)))
(def fn-unless-both (test x y ifneither)
(zap testify test)
(if do.test.x do.test.y
do.test.y nil
(do.ifneither)))
Hmm, interesting. If I were opting for another control structure (which I probably wouldn't in this case, but hypothetically), I think I'd make your ibona closer to this (not tested):
(mac unless-both (test x y neither)
(w/uniq (f fx fy)
`(withs (,f (testify ,test) ,fx (,f ,x) ,fy (,f ,y))
(or (and ,fx ,fy)
(and (no ,fx)
(no ,fy)
,neither)))))
(def congruent (x y)
(unless-both atom x y
(and (congruent (car x) (car y))
(congruent (cdr x) (cdr y)))))
(def congruent-sigs (x y)
(unless-both atom x y
(and (unless-both optional (car x) (car y)
(congruent-sigs (car x) (car y)))
(congruent-sigs (cdr x) (cdr y)))))
I'm not sure if there's a better name than unless-both. Also, you could conceivably make neither a rest arg and just splice it into the and so you don't have to do (unless-both atom x y (and ...)). I just think it reads better with the explicit and. (Of course, adding a macro here is probably overkill anyway. Fun, though!)
; Short for "if both or neither and," 'ibona checks 'x and 'y against
; 'test (which is sent through 'testify). If one is truthy and the
; other is falsy, this results in nil. If they're both falsy, this
; results in an 'and of the 'ifneither and 'eitherway expressions. If
; they're both truthy, this results in an 'and of the 'eitherway
; expressions if they exist, and it propagates the result of calling
; the test on 'y if they don't.
(mac ibona (test x y ifneither . eitherway)
`(fn-ibona ,test ,x ,y
(fn () ,ifneither)
,(when eitherway `(fn () (and ,@eitherway))))))
(def fn-ibona (test x y ifneither (o eitherway))
(zap testify test)
(if do.test.x (aand do.test.y
(if eitherway (do.eitherway) it))
do.test.y (aand (ifneither)
(if eitherway (do.eitherway) it))))
(def congruent (x y)
(ibona atom x y
(and (congruent car.x car.y)
(congruent cdr.x cdr.y))))
(def congruent-sigs (x y)
(ibona atom x y
(ibona optional car.x car.y
(congruent-sigs car.x car.y)
(congruent-sigs cdr.x cdr.y))))
I've found myself doing...
(if atom.x atom.y
atom.y nil
...)
...a couple of times myself, so I wouldn't be surprised if (some variation of) 'ibona were surprisingly useful. :-p
EDIT: Hmm, this doesn't abbreviate as much, but its implementation and description are much more concise.
; This is similar to 'whenlet, but for binary if-and-only-if (i.e.
; xnor).
(mac xnor2let (var x y . body)
`(fn-xnor2let ,x ,y (fn (,var) ,@body)))
(def fn-xnor2let (x y body)
(only.body:.y:if x idfn no))
(def congruent (x y)
(xnor2let it atom.x atom.y
(or it
(and (congruent car.x car.y)
(congruent cdr.x cdr.y)))))
(def congruent-sigs (x y)
(xnor2let it atom.x atom.y
(or it
(xnor2let it (optional car.x) (optional car.y)
(and (or it (congruent-sigs car.x car.y))
(congruent-sigs cdr.x cdr.y))))))