Tip: At least for me (on a Mac), running Arc with "[racket|mzscheme] -f as.scm" and then hitting ctrl-C at some point will kill the whole process, instead of dropping to a [Racket|Scheme] prompt from which I can use (tl) to return to the arc> prompt. Replacing the command with "[racket|mzscheme] -i -f as.scm", -i for interactive, fixes this. Much better.
I used to have 'cd ~/Dropbox/arc3.1;' before the 'rlwrap ...' part; the aload function and all the Arc files that load other Arc files (libs.arc) assume that you're in the Arc directory. I worked around this by wrapping all the calls to 'aload in as.scm like this:
Suggestion: The Arc toplevel binds the variables "that" and "thatexpr" every time you enter something: the expression typed in becomes thatexpr and the value of the expression becomes that. I think it would be really nice, and also easy, to add that feature.
With regard to (2), to destructively append the list '(1 2 3) to xs, you can:
(zap join xs '(1 2 3))
"zap join" is several characters longer than ++, but zap has general utility.
I use the string function to concatenate strings. It seems to work identically to +, as long as the first argument is a string. I do give heterogeneous arguments to string usually, and I like seeing that they will clearly be coerced into a string.
I have a couple of ideas.
1. It would be little problem for me if Arc provided + as is and provided a "plus" function that worked only on numbers and allowed me to simply go (= + plus) and proceed normally. Unfortunately, that would break all the functions in arc.arc and so forth that use + to concatenate lists (which includes, among other things, the setforms function, which is used in expand=). It would be really nice if one could "freeze" the variable references in those functions, so that changing the global value of + wouldn't change what "+" in the bodies of those functions referred to.
2. If you use concatenation so much, perhaps we could allocate an ssyntax character for concatenation. & is currently used for andf (experiment with ssexpand to see). We could boot out that usage, or perhaps have "&&" become andf. Good/bad idea? (Precedent: my memory tells me the TI-89 uses & for string concatenation.)
Regarding your second suggestion, we could also use . instead of &, as that's what Perl and PHP do - feels a little more natural to me. But . might cause me a little mental friction in differentiating between the different uses of . in (. "a" "b") and (do (= a '(1 2)) a.0).
To be honest, I'm still not crazy about the idea simply because I don't need the speed boost and + doesn't seem to cause me to use extra mental cycles when reading my code. I'd be open to it though if the community really wanted it that way.
We could vote and also ask PG what he thinks and then make a decision.
What you describe is doable with closures, at least as far as your example goes. Your example can be implemented almost word-for-word in Arc as it stands:
obj is a macro that creates a hash table (you don't need to quote the keys, by the way). So, (Socket url port) will return a function that accepts 1 or more arguments, assumes the first is a method name, looks it up in the hash-table o, finds the corresponding function, and applies it to the rest of the arguments.
If you want to give your object thing internal state that you'll want to modify, use a let.
Here is an example that probably demonstrates most of what you're looking for. It is pretty much cribbed from SICP[0].
Given the way I defined a Bank-Account, you could implement this rather simply:
(def deposit (x . args)
(apply x 'deposit args))
Or, with akkartik's implementation, which I agree is nicer and better:
(def deposit (x . args)
(apply x!deposit args))
And you could make another "object" and give it a distinct 'deposit "method" and it would work fine.
So... it seems that one can easily implement these things in Arc. If you have some project in mind for which you want OO stuff, you can just do the things posted in this thread, and tell everyone how it goes if you like.
If you think OO should be put into the core of the language, well, there's a certain resistance to adding features just because someone suggests it. The core would become large, disorganized, and difficult to maintain if we did that. So, you would have to give a convincing argument as to why it's worth putting in there, and a demonstration of how useful it is in some real project of yours is probably the best argument you could give.
This kind of OO with closures is a fun experiment and looks very elegant at first sight. I love the (x!deposit 50 "meh") version for its simplicity, the use of ssyntax, and the fact that you can pass x!deposit around as a first class function. Thanks to macros, you can of course easily come up with a nice syntax for the definitions:
However, the approach has some issues in real life use. First, every bank account instance replicates the method table and so takes up more memory the more methods the class defines, and each method is a closure that takes up memory as well. Also, this hash table obviously needs to be built every time an instance is created. Another big problem that follows from the above is that when you add or redefine methods on the class, existing instances are left with the old implementation. And there is no way to implement inheritance here.
I guess it is possible to remedy most or all of those problems by sacrifying methods as closures and instead do:
(= bank-account-mt
(obj check-pass (fn (self o pw)
(unless (is o!pw pw)
(err "Wrong password!")))
deposit (fn (self o x pw)
(self 'check-pass pw)
(++ o!money x))
withdraw (fn (self o x pw)
(self 'check-pass pw)
(if (< o!money x)
(err "Not enough money.")
(-- o!money x)))
check (fn (self o pw)
(self 'check-pass pw)
o!money)
change-pw (fn (self o new-pw pw)
(self 'check-pass pw)
(= o!pw new-pw))))
(def Bank-Account (password)
(let o (obj money 0 pw password)
(afn (method-name . args)
(apply (bank-account-mt method-name)
(cons self (cons o args))))))
Again using a macro to improve readability and writability. Adding inheritance is left as an exercise for the reader.
I'm sure this doesn't surprise you, but here's a quick version of 'defclass that uses a syntax similar to your first example and an implementation similar to your second example:
Nice, and you even changed it so x!deposit returns a function again! This does of course add some overhead since a closure is constructed every time you call a method, but still.
One thing I'm not quite happy with is that one has to write o!money. Would it somehow be possible to hide the o? Would it be possible to use !money or .money, or does the parser not allow that? And how to pass the hash table from the afn to the methods without polluting their namespaces? It could be done using a gensym, but then it is not possible to add methods to the method table outside defclass.
Due to the way eval works, it doesn't touch lexical variables. So:
arc> (let i 2 (+ i 1))
3
arc> (let i 2 (eval '(+ i 1)))
Error: "reference to undefined identifier: _i"
This isn't specific to Arc, by the way. In Racket and in Common Lisp, it doesn't work:
> (let ((i 2)) ;Racket
(eval 'i))
. . reference to undefined identifier: i
Thus, your version of when will not work the same way as the macro version when you try to do this, for example:
arc> (time:for i 1 1000 (mywhen '(is i 666) '(prn "Oh no!")))
Error: "reference to undefined identifier: _i"
Other than that little kink with lexical variables, though, using eval does do pretty much the same thing as using a macro.
One more thing: eval is also much less efficient if you intend to use it repeatedly. By definition, eval performs full syntax analysis every time you use it. In the below example, its argument is a constant, so a smart compiler could theoretically optimize it to run like normal code, but in general its argument would not be constant.
So, I recommend against using eval if you can avoid it. One use case is where you wait for the user to type in an expression, and you evaluate it and print the result.
Thank you for the answer, very interesting. Here's some more things I found:
1) actually the "reference to undefined identifier" error can be avoided un-quoting the variables at calling time, as in:
(let i 2 (eval `(+ ,i 1)))
(time:for i 1 1000 (mywhen `(is ,i 666) '(prn "Oh no!")))
2) the last expression is computed in 176 msec, whereas the regular macro 'when' is computed in 2 msec (much faster as you forecasted).
I did a couple of tests, and stuff that involves a lot of set-c[ad]ring seems to happen 3-4 times as fast. Nice!
;All tests done using "racket -i -f as.scm", Racket v5.0
;Where x is (n-of 1000000 (rand))
;With mpairs
arc> (time:no:sort < x)
time: 12408 msec.
nil
;Normal arc3.1
arc> (time:no:sort < x)
time: 36004 msec.
nil
;This function destructively reverses a list, as in CL
; but it's actually slower than 'rev, for some reason
(def nreverse (xs)
(and xs
(let tail nil
(while xs
(let rest cdr.xs
(= cdr.xs tail
tail xs
xs rest)))
tail)))
;With mpairs
arc> (time:no:zap nreverse x)
time: 719 msec.
nil
;Normal arc3.1
arc> (time:no:zap nreverse x)
time: 2732 msec.
nil
On the other hand, it seems to slow down normal list operations somewhat, about 20%.
;The format of the output of "time" is "time: nnn msec."
; so we will take the output of a bunch of iterations,
; read all sexprs, and take the second of every three.
;With mpairs
arc> (map cadr (tuples (readall:tostring (repeat 30 (time:len x))) 3))
(66 65 63 63 62 62 62 63 66 63 63 69 74 66 63 371 97 92 74 71
71 71 64 71 63 71 63 63 63 63)
;With normal arc3.1
arc> (map cadr (tuples (readall:tostring (repeat 30 (time:len x))) 3))
(62 62 61 52 52 52 52 52 52 51 51 51 57 52 52 56 56 61 61 52 52
52 52 54 56 61 52 52 54 61)
On the whole, probably worth it.
And, by the way, for your amusement and my pleasure, I have a dinky little version of mergesort that runs 20% faster than the destructive version defined in arc.arc runs in arc3.1. It's stable, too. http://pastebin.com/XFz6T9xW
I'm not sure about the appendfile--given what infile and outfile do, it sounds like a procedure that creates an output-port that appends to a file. Maybe to-appendfile, appendtofile, appendingfile, tofile/append... Alternatively, we could make keyword arguments happen in Arc, and then you would just throw ":append t" or something inside the call to tofile. That would also allow for further extension with, e.g., an :if-exists argument.
How about 'tolog? Are files opened for appending for other reasons, in practice? This would also keep with the to-means-output, from-means-input pattern.
I'd try to err on the side of generality. And I'm not quite as concerned about to:output / from:input, if the names are still "clear enough".
As to waterhouse's suggestions, I had considered those names. I suppose if you read appendfile as a noun instead of a verb-and-noun, it's confusing (though infile and outfile don't really have the same problem, so it's not the train of thought my brain follows). It's hard modifying a name like tofile with a long word like append. We already have two words in tofile, so adding a third without hyphenation is stretching it, and adding hyphens breaks the flow with the other names (fromfile, tostring, etc.). We could go for something shorter, like addtofile, which delineates itself well without hyphens because each word is one syllable. If we can't avoid hyphens, using / instead (e.g., tofile/a or tofile/append) flows better, but isn't that great.
Another name that occurred to me -- and is probably my favorite so far -- is ontofile, which is still simple enough to not need hyphens, communicates intent (appending something onto a file), and worms the word to in there, painting it with the to:output / from:input correlation. Thoughts?
Another name that occurred to me -- and is probably my favorite so far -- is ontofile, which is still simple enough to not need hyphens, communicates intent (appending something onto a file), and worms the word to in there, painting it with the to:output / from:input correlation. Thoughts?
+1! ontofile is a great name, in my opinion, for all the reasons you listed.
I searched for a good portmanteau in the vein of mappend, but I don't think there is one. fappend? Sounds like frappuchino. filepend is decent, but I think I prefer ontofile.