As with most criticisms of Arc, these complaints are mostly treating Arc as if it was something other than a VERY early release of a language that's still under development. Both the syntax and semantics of Arc are expected to change as new features are considered/added/rejected.
However, it's easy to see why the author would think of Arc as something more than it is, since it was so hyped by so many people online and since it's been under development for so long. Plus, given the length of time that it's been out, most of us expected more progress by now.
Of course, this doesn't excuse the article's shortcomings, but I think there are a couple of valid points buried amidst the specious ones, so I'll respond to them all one at a time:
1. Criticizing Arc for a lack of features is pointless. It's an early release of the language, and there's no reason why more features can't be added later.
2. Paul Graham wrote an entire article on "Why Arc is not especially object-oriented" which links to several other articles by both himself and others discussing the pros and cons of OOP. The lack of namespaces is a valid criticism at the moment, especially since a lack of a namespace mechanism makes it more difficult for people to write and share Arc libraries.
3. Arc hasn't had enough releases for me to care about having access to PG's version control repository.
4. I'm not sold on Test-Driven-Design for software which is expected to radically change numerous times before becoming stable. I generally write tests after I've somewhat stabilized a codebase, and only then do I make any effort to keep my tests up to date. This is a nuanced issue and I respect the TDD arguments, but this certainly doesn't turn me off to such early versions of Arc.
5. This is another complaint about a missing feature. I agree that this feature should exist, but things like this don't sink the language.
6. Unusual version numbering is a deadly sin?
7. PG's stated outlook is that the more common an operation, the less intuitive its name can be. I don't find "no" less intuitive than "not", although I think he has a point about it being silly to abbreviate "print" to "pr" just to save 3 characters.
8. Line numbers is errors is my personal #1 desired feature for Arc, and hopefully this feature will be added soon.
Interestingly, his conclusion is entirely correct: "In short, if you were hoping for a usable and modern dialect of Lisp, then Arc is not the answer, and won't be without a lot of work." Arc will indeed take a lot of work to become modern and usable for most tasks, and I'm disappointed that PG hasn't put out a new release in over 9 months, but I hope that he'll eventually find time to take a more active role in Arc's development.
I'd love for PG to spent more time per month on Arc, since to me personally it's probably the most interesting thing he does. I enjoy his essays and think YCombinator has produced some great startups which I sometimes use, but Arc is more interesting to me than either of those things.
With that being said, I knew all along that Arc is an experiment in trying to make a 100 year language, not something that was intended to help me deploy commercial applications as soon as possible. So I've played around with Arc a little and been happy to have the experience.
I'll play around with it more as it gets more advanced and gets more libraries and such. Until then, I'll remain a happy Python programmer and look forward to whatever PG and the rest of the Arc community slowly end up evolving towards.
It seems like a module system could mitigate this problem. In other languages, functions resolve variables by first looking in their local scope and then by checking to see if the variable is global to the module/package in which it was defined.
I guess this is harder for macros, since they simply alter your code in-place and after the macro is executed, you have no way of knowing where the code came from. However, if we did have a module system, then you could refer to cplx->cplx-fun or whatever the module notation would be and thus the problem wouldn't occur.
Of course, this would require saying cplx-> or something like that in front of all of you calls to internal module functions/macros, which would be about as cumbersome as the proposed solution.
Perhaps we could have a type of macro which adds these module-specifying prefixes to your function calls when the macro is expanded. Of course, without knowing how the module system would even work, it's hard to gauge whether this would ultimately make things better or worse.
Depends on how the module system is constructed. If modules are first class objects and not a set of symbols like CL packages are, then the module name itself may be shadowed by a local, i.e. 'cplx itself could fall victim.
> Perhaps we could have a type of macro which adds these module-specifying prefixes to your function calls when the macro is expanded
CL packages solve this problem fine. I was hoping Arc wouldn't have to go down that route (because it always confuses newbies and adds a lot of complexity) but the more problems arise, the more I appreciate how good packages are.
I actually used "obj" as a reference when writing "new", which is why they were so similar. I decided not to use a gensym because I figured that "this" would effectively be a reserved word with a common meaning and so that I could use "this" in my methods. However, your point about clashes is well taken.
As for "defm", it looks pretty nice. I guess I wouldn't mind calling "(until p 30)" instead of "(p!until 30)" so long as I don't have to call "person-until" just to prevent naming clashes.
I really dislike having to call "((rep self) 'first)" instead of "self!first", but as you point out, I might be able to define some macros to make this less verbose.
I guess for now I'll start playing around with Anarki. Hopefully the more knowledgable Lispers out there will settle on an object system that's general enough to be useful and concise enough to by enjoyable.
Ah, I see -- new is an auto-binding obj. That makes sense if you're including methods (which perhaps you shouldn't). It's just that usually when people do that it is a mistake :)
Actually, that was a mistake on my part; you can in fact write
(defm full ((t self person))
(string self!first " " self!last)
(defm until ((t self person) year)
(- year self!age)
because the defcall allows you to put objects of type person in that position. And now the only redundancy left is having to write (t self person) all the time, which you may or may not want to abstract.
The other missing feature is the inability to say (= p!age 42); for that, you need to override sref:
; Insert error checking so that you can't
; write (= p!species 'banana).
(defm sref ((t self person) value key)
(= ((rep self) key) value))
Again, <plug>the tagged unions do all of this for you (except for defining your own methods, which you have to do with defm and vcase/tcase)</plug>. Of course, they don't have inheritance.
That sounds excellent, and it makes me a lot more excited about using objects in Arc. I don't care that much about having to write (t self person) a lot, since it seems like an acceptably low amount of boilerplate.
However, out of curiosity, why do you have to write "t" instead of just saying "(self person)"? I read through the code for defm and argmap, but my Lisp reading skills aren't strong enough to decipher it.
Because parameter lists in Arc support destructuring. What that means is that anywhere you can write variable to bind a name to a value (such as in (let variable 10 (prn variable))), you can also write (v1 v2) to bind a list's first element to v1 and second element to v2. And (o v default) denotes optional parameters. Perhaps some examples would be clearer:
(with (i 1 v 5 x 10)
(let a (list i v) (prn "a is (1 5)."))
(let (b c) (list i v) (prn "b is 1 and c is 5."))
(let (d . e) (list i v x) (prn "d is 1 and e is (5 10)."))
(let (f (o g)) (list i) (prn "f is 1 and g is nil."))
(let (h (o j 42)) (list i) (prn "h is 1 and j is 42."))
(let (k (o m)) (list i v) (prn "k is 1 and m is 5."))
(let (n (o p 42)) (list i v) (prn "n is 1 and p is 5.")))
defm adds a (t var type) syntax; if you left out the t, you would have ordinary destructuring bind.
Does this give you any kind of "this" object in your object methods? That's a large part of what I'm looking for, since that lets me say "(p!until 30)" in my example, or something like "(craa!act_bite)" in your example.
You are correct; it's also been awhile since I played with Javascript, but I just checked "A Reintroduction to Javascript" then it listed both my way and your way as examples, describing your way as being cleaner: http://developer.mozilla.org/en/docs/A_re-introduction_to_Ja...
Wow, that's much better! I didn't think that would work, since I thought that Arc's lexical scope bound variables in inner functions to the current value of those variables. Apparently I was wrong; thanks for the tip.
I like that "subseq" was renamed to "cut" and that it now takes negative indices.
However, I wish we could say (x 1 -1) instead of (cut x 1 -1). And since x.0.0 expands to (x 0 0) then that would let us say x.1.-1 which is what languages such as Python and Ruby already allow.
I hope that this is coming; I image that Arc's creators don't want to introduce too many changes all at once, so maybe they'll add this after they see how the x.y syntax works out.
Hmm, yes, so would I. I should fix that. I just did what Emmett suggested, which must have been based on Ruby, but it doesn't actually seem like a good idea.
Agreed. Ruby does it the other way ("abcde"[1..-1] is "bcd"), but that's only because there's no other way to specify "until the end." But that's the default in Arc; the following are equivalent right now:
Both nil and (len str) work. I see both sides of the argument as counting from -1 eliminates the 0 corner case.
I like indices that are intuitive with literal numbers. Counting from 0 at one end and from 1 at the other is jarring. When -1 points to the end of string rather than before the last char (cut str 0 (- (len str))) returns the first char instead of the empty string.
With -1 -> before last char:
(def chop ((o str "abcdef"))
(pr "Chop how many chars off the end of \"" str "\"? ")
(= n (coerce (cut (readline) 1) 'int)) ; bug in readline prepends #\newline
(prn "Chopped: \"" (if (is n 0) str (cut str 0 (- n))) "\"")) ; handle corner case
With -1 -> end of string:
(def chop ((o str "abcdef"))
(pr "Chop how many chars off the end of \"" str "\"? ")
(= n (coerce (cut (readline) 1) 'int)) ; bug in readline prepends #\newline
(prn "Chopped: \"" (cut str 0 (- -1 n)) "\"")) ; no corner case, but there's this -1 there
I probably made a stronger argument for -1 pointing to the end of string as it leads to shorter code.
While I agree that this shouldn't be abused, I'm not actually proposing this syntax; Arc already lets you say cut.x.1.3 as an alternative to (cut x 1 3).
I suspect that the only time I'd ever want to chain together several different things with . is when I'm doing list slices. Which is why I'd like the x.1.3 syntax to work, since in my opinion cut.x.1.3 is a lot uglier, perhaps enough that I'd rather not do it.