Lisps pass some objects (lists, hash tables) by reference, but others (integers, symbols) by value. In languages that offer it like C++, explicit pass-by-reference is useful because A) these languages don't deal with objects as references by default and B) to pass extra information to the caller of the function (e.g. having a boolean reference that's set to true if some condition is met).
However, A is a non-issue in Lisps and B is trivially solvable by returning a pair, so I don't see how pass-by-reference could be useful.
Consider deleting a node from a binary tree. You want a function that looks at a node and, if it's a match, unlinks it from its parent. Passing by reference allows you to do this cleanly. The alternative is to peek ahead at each subnode (messy) or to pass information up the call stack (inefficient, as the function can no longer be tail-recursive).
I'm not advocating C++ references, which I think are too implicit. If a function call foo(x) can change the value of x, there needs to be some visual indication of this. I'd prefer something like plain C pointers, with which you can pass the address of an object: foo(&x).
Of course an alternative is store objects wrapped in containers, and this isn't too bad a solution.
It would be really nice if this could be built into the standard def. Not with this syntax, naturally, but perhaps detecting if any pattern-like parameters showed up:
Some nitpicks - (x . y . zs) should be (x y . zs) - note that this was buggy at the time you wrote your post, but I have just posted a bugfix on your arc-wiki.git. Also, the builtin pair uses (list (list (car xs))) for the ((x)) case.
Also, the definition of factorial above will crash on (factorial -1) but then so does Haskell if you defined it like that ^^.
If you think of the . here as Haskell's cons (:) then it makes sense. All the same, (x y . zs) can mean the exact same thing, not to mention it is shorter and familiar.
Arc uses the PLT Scheme reader. The PLT Scheme reader has a non-standard (as compared to RnRS) extension, namely the double-dot notation. The double-dot notation is used to write "infix expressions".
Consider:
(x . < . y) is turned into (< x y)
(integer? boolean? . -> . void?) is turned into (-> integer? boolean? void?)
This reader extension can be turned off, but setting the appropriate parameter.
That is strange, and probably a bug. You can use up to 2 dots and the last dot is handled first, consing x and (z . nil) then consing y and (x (z . nil)). '(w x . y . z) gives (y w x z).
I can almost see how this bug appeared. Searching for the first part of a dotted expression yields (w x), and then searching for the 2nd part yields z. For some reason the expression becomes '(y . (w x z)); that I cannot explain without looking at the code.
The way I solved this in the Wiki was to give tagged values (except macros) the same calling semantics as their non-tagged equivalents. Thus, you can represent arbitrary datatypes as functions, tagging them to keep track of their type but still calling them.
a) The ability to define within arc what happens when a list is
used as function, instead of it being predefined in scheme
b) The ability to do this for any type and custom types.
And potentially:
c) For annotated values to be able to do this per-object,
by storing an extra field in there that is the getter,
where the default value is the one for the type.
E.g: (annotate 'blabla value getter)
If there is no getter, then use the one for type 'blabla
Equal? recursively compares the contents of pairs, vectors, and strings, applying eqv? on other objects such as numbers and symbols. A rule of thumb is that objects are generally equal? if they print the same. Equal? may fail to terminate if its arguments are circular data structures.
There is a silver lining though. In R6RS the following was added:
Note: The equal? procedure must always terminate, even if its arguments contain cycles.
That is, there is a good chance that the behaviour of equal? changes, when the PLT Scheme team begins implementing the R6RS language.
I submitted a bug report to PLT Scheme about that. Here's their answer :
"The process for generating an `equal?' hash key is defined to not
terminate on circular objects, such as a hash table that contains
itself. So `(hash-table-get <any-table> t)' loops trying to generate a
key for `t'.
The specification of hashing has changed for the next release to deal
gracefully with cyclic data, just as `equal?' has changed so that it
can compare cyclic data. In the current pre-release version,
v3.99.0.10, your example returns immediately.
(It returns with a "key not found" error. That's because the process of installing a hash table into itself changes the table's own `equal?'
hash key.)"
By all means commit this to the repo. The idea of a wiki is "push first, ask questions later." It's by no means critical that the wiki stay stable; that's what git://nex-3.com/arc.git is for.
Git makes a distinction between your local repository and remote repositories. You have to commit your change to your local repo before you can push it to the remote one. So first you "git commit," then you "git push."
The total workflow goes something like this:
git clone git://nex-3.com/arc-wiki.git
emacs lib/import.arc # Make your edits
git add lib/import.arc # Schedule lib/import.arc for committing
git commit # Commit lib/import.arc to your local repo
git push # Push your local changes to the remote repo
The "git add" step is due to a little idiosyncrasy of Git where "commit" doesn't commit any changes you don't explicitly tell it to via "git add." You can also do "git commit -a" to automatically add all changes before you commit.
Also, "git commit" takes the same -m parameter that "svn commit" does.