You know, I really want to like Clojure, but so far I'm finding it a bit of a slog, for the following reasons:
1. stack traces are very hard to decipher
2. ubiquitous laziness can lead to some really subtle bugs
3. the syntax can be extremely obtuse, moreso than other lisps
4. the basic ADT is just a keyed map, which provides a lot of flexibility but also can make data modeling very opaque, even compared to other dynamic languages
5. immutable and recursive as a default takes a lot of getting used to. things I could dash out in minutes in ruby take me hours sometimes
6. other languages have caught up enough that Lisp macros are not as game changing as they might have been ten years ago
I embarked on a project recently to explore machine learning algorithms, implementing them in both Scala and Clojure at the same time. I expected this to be an easy win for Clojure but, to my surprise, I'm much more productive in Scala, my code has fewer bugs, and I can refactor it much more aggressively. I realize that this isn't an either/or thing and it may be entirely my shortcomings at play here, but my instinct is that although Clojure is a superb lisp, it's still a lisp, and it's going to be a niche that most programmers won't choose to occupy.
Absolutely, but these aren't ends of the same stick. You can have immutable but eager, which is simpler to model mentally a lot of the time, at least for me. Simon Peyton-Jones has said that perhaps Haskell should have made eager evaluation the default and I'm inclined to agree that lazy evaluation is very powerful but maybe not a good default.
3. Sure but no more than Haskell or any other terse FP lang, Scala included
True, although the baseline for a lot of Clojure code is higher, IMO.
4. defrecord works wonders
Maybe so. I haven't really gotten my head entirely around the options for datatypes in CLJ yet.
5. It takes me hours to do things in Ruby that I could dash out in Clojure.
It's true. Clojure makes some things that are hard to do in other languages much easier. Programmers are different, so we shouldn't expect one set of tools to suit us all equally well.
Clojure makes some things that are hard to do in other languages much easier.
I once heard it put that "Functional programming makes all problems equally hard." When taken figuratively, it does make a lot of sense: the things that are hard to do functionally tend to be things that are easy to do procedurally (e.g. UI stuff -- the code obviously, not the design).
"2. ubiquitous laziness can lead to some really subtle bugs"
I find myself implementing Iterable wrappers more often in Java now, largely due to the influence of Clojure lazy seqs. Then I can use the abbreviated for loop syntax, for example, without creating a ridiculously large ArrayList in memory.
I know that the Iterator pattern has been around for a long time, but I never realized how broadly it could be applied to use high level iteration constructs while keeping a minimal memory footprint, before I got used to Clojure.
So Clojure has made me a fan of lazy evaluation, even in Java.
You can make you code quite "lazy" if you practice enough, and eventually it just becomes a habit. Whenever you do anything you start asking yourself : "can I do this in a lazy way?"
Iterators in Python are slightly different from lazy lists in languages like Haskell and Clojure. In Python, iterators are stateful; calling next() moves the iterator on, discarding the previous head. In Clojure, a seq has no state, and the head is not automatically discarded when the next item is accessed.
Regarding item 1, one of my friends is a professor and tries to fund a student to help with making Clojure stack traces more understandable. I will not say who that friend is and what university or college s/he is in (s/he has not authorized me to say this), but if anyone thinks they can help with funding I will contact the said friend. Thanks!
> 2. ubiquitous laziness can lead to some really subtle bugs
After the recent compiler change to prevent accidental head retention of lazy sequences, I don't remember having faced any subtle issue with laziness. Can you give me an example of what bit you?
I don't have a concrete example at hand but the pattern is this:
1. program crashes with cryptic stack trace
2. add a println to look at the data
3. program crashes again, but can't print anything because lazy evaluation of something in the data structure triggers a new, different crash
So you've got an error you don't understand and you can't even look at the data you're working with so you start the hunt & peck procedure of trying to find the last point at which your data was sane so you can work back from there. Add dynamic typing to the mix and things get really ugly. I spent an hour tearing my hair out the other day when I inadvertently reversed the arguments to a function but didn't get an error until three function calls later because the types were duck-compatible that far.
So far I've implemented a neural net, a naive bayes classifer, a decision tree builder, and a couple of clustering algorithms. So far Scala has proven to be an excellent vehicle for this. People say that static languages are better for big teams but I'm finding the type system to be a huge help in solo coding. It makes it so much easier to pinpoint stupid bugs, forces me to think more carefully about the architecture of my code, and makes me much more confident about constant and aggressive refactoring. Also, a lot of ML algorithms look a lot less scary in a concise, high level language like Scala.
The main hurdle has been coming to terms with the Scala collections library. It's a fantastic package, but there's a lot there and it takes some time to learn how the different collection types work and how they interface with the type system.
TL;DR - I'm not sure I could have picked a better language for this.
1. stack traces are very hard to decipher
2. ubiquitous laziness can lead to some really subtle bugs
3. the syntax can be extremely obtuse, moreso than other lisps
4. the basic ADT is just a keyed map, which provides a lot of flexibility but also can make data modeling very opaque, even compared to other dynamic languages
5. immutable and recursive as a default takes a lot of getting used to. things I could dash out in minutes in ruby take me hours sometimes
6. other languages have caught up enough that Lisp macros are not as game changing as they might have been ten years ago
I embarked on a project recently to explore machine learning algorithms, implementing them in both Scala and Clojure at the same time. I expected this to be an easy win for Clojure but, to my surprise, I'm much more productive in Scala, my code has fewer bugs, and I can refactor it much more aggressively. I realize that this isn't an either/or thing and it may be entirely my shortcomings at play here, but my instinct is that although Clojure is a superb lisp, it's still a lisp, and it's going to be a niche that most programmers won't choose to occupy.