I actually have very similar thoughts on clojure versus racket. I admire both languges for different reasons, but if I were to pick one to get the job done it would be Clojure. I cry every time I see the idiomatic construct in any Scheme of converting a vector or string into a list just so you can do the normal core operations on it, and then converting it back to a vector or string, but this kind of code is quite common due to the design of almost every Lisp.
Another issue I have with racket, which the article touched on a little, is the overall lack of cohesion between different libraries and language features. I believe this is an unfortunate consequence of starting out with such a simple core language as all Scheme's do. Everything that is built has a different style to it and doesn't necessarily interoperate well with other parts of the language.
For example, structures are entirely orthogonal to objects and the class and interface system are entirely orthogonal to units, even though these two pairs of features can heavily overlap in their use cases.
When it comes down to it, if I want to get something done in Clojure I feel like I have a clear path as to how to go about it, but in Racket it seems much easier to get lost inside the language for the reasons mentioned in the article.
> this kind of code is quite common due to the design of almost every Lisp
Scheme != Lisp (maybe a "lisp")
I was trying to figure out what you mean from a Common Lisp perspective. Are you referring to the author's example?
All of Clojure's collections can be treated as if they
were a list, and you can write algorithms to traverse
them using the same pattern of empty?/first/rest that
you'd use on a list. This means that all the powerful
higher-order functions like map/filter/reduce work just
as well on a vector as a list.
Just off the top of my head, CL's map and reduce can operate on lists or vectors. Pretty much anywhere you see the word "sequence" in the spec, you can work with a vector or a list (and several other types). I can understand the point that it might be awkward in Racket; does it get awkward in CL?
I cannot speak for Racket/Scheme's limitations, but I am looking to see where Clojure might be a better choice for me than CL other than interoperability with Java. I have not seen a good data structures argument yet.
CL ostensibly comes with a built-in sequence class. The problem is none of the various utility methods that are included are generic functions, so in practice it doesn't do much good. Christophe Rhodes worked on an extensible sequence standard:
In summary: CL comes with some nicer datastructures than Scheme (hash tables, multidimensional and adjustable arrays), but other than that it has the same problems.
My takeaway for this is this -- If the utility methods are not generic functions, it is fairly trivial to write a generic function that wraps around the utility method and then play with additional types as needed. So if the problem with the data structures in scheme and Lisp are that people are spending a whole lot of time repeating conversion patterns, shouldn't those people be developing abstractions, at least for their own use/libraries?
When I read this kind of complaint, I get the feeling people tend to want Batteries Included Their Way. They do not go the one step further to wrap things in the manner they desire. Like-minded folks do not get the benefit of those wrappers. jQuery was a hit because someone deployed his library of common JavaScript patterns.
The problem is that any time you want to interface to code outside of that library, you run into the same "convert vector to list" problem the OP was talking about. The solution really is to cram down Rhodes' proposal into every implementation's throat, so that all existing code can start handling new sequence types.
While this is a decent article in its core argument, it's a bit misleading in some of the details.
Methods are not true closures, and can't be passed directly to higher-order functions.
You can trivially wrap a Java method in a fn, and Clojure has syntactic sugar (the #() notation for anonymous function literals) to help with this.
Slow numeric performance.
This is true in the 1.2 release, but the next version of Clojure addresses this problem in (IMO) a very elegant way. Search on the mailing list for discussion of the num branch for more information. If numeric performance concerns your problem domains, please use the branches that resolve this.
Slow startup time.
Yes, the JVM starts slowly. I restart Clojure REPLs about once a week. I don't exactly notice this problem. Clojure isn't like Python or Ruby or Perl, where you start new interpreters all the time.
Very interesting perspective. I never thought about it that way, but it's true that one of my principal pleasures working with Clojure was about the data structures, and their very nice integration into the language.
The concurrency features sounded exciting but were never of any use to me. But the consistent and extremely powerful data structures and associated libraries were my main subject of wonder, even if i didn't really notice it.
Now that i'm working with python all day, even though i really really like this language, i can't help but hate the inconsistent way data structure works (and the fact that they are mutable, and that mutable and immutable operations are mixed up in the API without a clue). And i used to consider python as being quite nice data-structures wise.
This guy puts his finger on precisely the thing that might get me to switch from Common Lisp to Clojure someday. The Java interop aspect is vastly overrated on a technical level. (That's not to say it is overrated in general; it's a marketing master stroke without which the language would have been stillborn.) The concurrency innovations are at best experimental, seem kitchen-sinky, and don't attract me much. But boy do I envy those data structures.
> The concurrency innovations are at best experimental, seem kitchen-sinky,
Could you expand on that statement. I don't think there is anything experimental about Clojure's concurrency features. They are quite robust and build on proven technology.
Clojure has a number of cool new ideas, but many of them are unproven, and only time will tell whether they are truly valuable.
We don't know yet what will become the dominant concurrent programming style(s). For all the talk about the multicore future, nothing has yet emerged that's compellingly better than traditional approaches. Or maybe it has and it just hasn't made its way through the noise into general consciousness; I don't know. Clojure's approach here strikes me as a sort of experimental groping: add several different new concurrency primitives and see what sticks. That aspect seems quite different from the improvements Clojure offers that came from years of experience programming Common Lisp and a resultingly deep intuition about what constructs would make it more comfortable.
Perhaps Clojure has hit the concurrency jackpot and come up with a better way of writing parallel programs. That would be significant. It would even be significant if it offered a better way of writing a particular class of parallel programs. So far, though, I haven't seen convincing evidence of that. I'm more excited about Clojure being an incremental improvement over Common Lisp that is winning over a new audience of hackers.
Racket's hygienic macro system seems to be a heavier cognitive load as well (although it certainly has its own benefits, and some would say it's essential). I really like in Clojure and Arc how easy it is to pound out a macro when you need one.
I have less experience in CL and Racket. I would think that since CL's macros are unhygienic, they would be easy to write as they are in Clojure. It could be that I just haven't gotten the hang of Racket's macros yet, and that once you do they aren't any harder. Let me know if this is the case.
For purposes of writing macros, CL seems to be identical to Arc, other than the name for the macro-defining construct ("defmacro" instead of "mac"). Which, of course, can be fixed with a macro.
There are three "things" about Racket's macro system: hygiene, pattern-matching, and phases. The sort of built-for-you way to do things (define-syntax with syntax-rules or syntax-case) is both hygienic and pattern-matching-based. But you can also make something that is hygienic but not pattern-based, or pattern-based but not hygienic, or neither; this last one is done for you in the library "mzlib/defmacro".
However, what you can't do, at least not without doing something like creating a new file and inserting (require (for-meta 2 <new file>)), is define a macro that you use in a macro body. For example, if you wanted to define "w/uniq", so that you could write
(define-macro (swap a b)
(w/uniq tmp ;instead of (let ((tmp (gensym)))
`(let ((,tmp ,a))
...etc
Simply writing (define-macro (w/uniq var . body) ...) will not work; a macro must be defined at phase level 2 for it to be used by a macro that is defined at phase level 1 (which is when macros are normally defined). And I have not succeeded in finding any way to make this work other than, as I said, creating another file and requiring it. (And, of course, if you want to use anything that isn't base Racket when defining 'w/uniq or whatever, you'll have to require that somehow, or do without.) This might be a minor complaint, but it does annoy me.
First, you don't need `w/uniq' in Racket - hygenic macros do that for you automatically. Here's `swap' in Racket:
(define-syntax-rule (swap a b)
(let ([tmp a]) (set! a b) (set! b tmp)))
But if you wanted to implement it, here's an easy way to do it in the same module
(define-syntax (swap stx)
(define-syntax (w/uniq stx) ...)
(syntax-parse stx
[(swap a b) ...]))
That said, the fact that you can't define macros to be used in macros in the same file can be annoying, and is something that we plan to add to the language.
Where Racket has a clear edge on Clojure is learning about it. http://racket-lang.org/ is very good, and the included IDE means you can hit the ground running and start typing code from a tutorial within minutes.
Clojure by comparison is like moving to a new city and trying to make friends (with emacs).
Racket is good that way, but you can get immediate gratification from things like Try Clojure too.
And of course, the Clojure setup is actually attractive if you're already using Emacs, especially since it offers integration with SLIME. No one has been able to do that with Arc yet, as far as I know. (With Racket, I'm not sure.)
I had horrific experiences trying to get Racket namespaces to work correctly (in Racket, a namespace is a value rather than simply a name). I found I couldn't get files to use the same namespace no matter how hard I tried, and without a specific namespace some code I was eval-ing simply refused to work.
I suspect Clojure lacks this 'feature' :-)
It's frustrating because I'm sure Racket has a lot to offer but not being able to do something really simple (even after hours and hours of trying) completely put me off.
Some people love Clojure specifically because it sits on top of Java and gives them access to their favorite Java libraries. Frankly, I have yet to find a Java library I'd actually want to use. Something about Java seems to turn every library into an insanely complex explosion of classes, and Java programmers mistakenly seem to think that JavaDoc-produced lists of every single class and method constitutes "good documentation".
True, and eff-you-forever Taligent for unleashing this stylistic horror on the world.
But the flipside of this is positive. It feels incredibly good to take a Java library down into your mad scientist workshop and come back out with a beautiful, functional, efficient, and useful interface to a previously ugly library interface. A great example of this is how most Clojure programmers use Jetty. Just pass in a lambda!
It's more the ability to deploy my apps into a Java environment that we like. Yes, it is possible to deploy CL/Scheme in production, and lots of smart people have done it successfully. With Clojure, I can build my app into a .JAR, and deploy it on most desktops. Also, a typical enterprise sysadmin will have no problem putting this into a J2EE (pays the bills) technology stack.
On the JVM that number might be a bit higher, but there are some excellent gems in there too. Lucene comes to mind especially; there's nothing like it on any other platform.
Love it or hate it, Java's back catalog of libraries is gargantuan. One of the biggest obstacles in getting a new language adopted is library support. The fact that Clojure gets all that for free is a way to get people using the language now. As Clojure matures, I'm certain more and more native libraries will emerge negating a lot of the need for Java.
I absolutely agree, but if you have no choice but to cooperate with the usual "enterprise" hodgepodge with its myriads of standards and protocols, not having to write something that will allow you to do that, is a huge benefit.
You'll have to suffer the environment, you don't have to suffer the language.
But I think it's going to be interesting whether the clojure community will re-write the innards of the libraries they're wrapping already. Will we see e.g. compojure that's turtles all the way down?
Many huge Java frameworks are painfully over-complex, but personally I make good use of a lot of "tiny" Java libraries. e.g. if I want to calculate an MD5 checksum or pluralize a word, there's usually a bit of Java out there for that already. Joda-Time is a good example of a clean Java library that's fairly painless to use from Clojure.
And then sometimes people have written Clojure wrappers around Java frameworks. Ring/Compojure as a thin layer over Java servlets is a good example. clojure.contrib.sql is a thin wrapper around JDBC, so that Clojure can already talk to a wide variety of databases.
In that case you get the benefit of a solid codebase as well as the benefit of a clean Lispy interface. Not sure how much more you could ask for. It would likely take years to reproduce all of that work in pure Clojure.
Another issue I have with racket, which the article touched on a little, is the overall lack of cohesion between different libraries and language features. I believe this is an unfortunate consequence of starting out with such a simple core language as all Scheme's do. Everything that is built has a different style to it and doesn't necessarily interoperate well with other parts of the language.
For example, structures are entirely orthogonal to objects and the class and interface system are entirely orthogonal to units, even though these two pairs of features can heavily overlap in their use cases.
When it comes down to it, if I want to get something done in Clojure I feel like I have a clear path as to how to go about it, but in Racket it seems much easier to get lost inside the language for the reasons mentioned in the article.