Clojure is over two years old, and only last week did I finally get round to writing something in it. Some reactions after writing a few hundred lines:
- Error reporting is often a bane of new languages, but Clojure's is pretty good — usually you get a Java stack trace. However, I did get a number of
NullPointerExceptions without stack traces.
- I often made what must be a standard newbie mistake: using parentheses in place of square brackets. Some forms, like
when-first, handle that well:
(when-first (x nil) 3)java.lang.IllegalArgumentException: when-first requires a vector for its binding (NO_SOURCE_FILE:0)
fngives a singularly confusing error:
user=>This could be easily fixed by checking the type in
(fn (x) 2)java.lang.RuntimeException: java.lang.IllegalArgumentException: Don't know how to create ISeq from: clojure.lang.Symbol (NO_SOURCE_FILE:136)
(if (not (vector? params)) (throw (java.lang.IllegalArgumentException. "fn requires a vector for its parameter list")))
- Accessing nonexistent fields of struct-maps gives nil instead of an exception. This is consistent with other dictionaries, but it means this error isn't detected reliably, even dynamically.
docis the first thing on the cheatsheet, but I didn't notice it until after I'd spent much of a day manually looking things up. :/
- The documentation for
proxysays it “expands to code which creates a instance of a proxy class that implements the named class/interface(s) by calling the supplied fns”, which sounds complicated and potentially inefficient. So I was very suspicious of it until I realized it's just inner classes.
- Something like half of my total difficulties were about the Java libraries, not Clojure.
- Java's GUI libraries are not entirely easy to use, but it's still wonderful to be able to take GUI support for granted. I'm used to GUI being possible in theory but not in practice.
rangedid exactly what I wanted, with no mental effort.
dosynctook some getting used to.
alteraren't unfamiliar, but having to announce in advance that you're doing state is a little unnerving. I didn't have any problems with code that might or might not be called in a transaction, but I was afraid I might. I'm not used to keeping track of this.
- The inability to nest
#(... % ...)is annoying. In about 200 nonblank noncomment lines, I used it five times. It would have been seven, but two of those would have been nested (immediately, in both cases) inside others. On the other hand, there were several other 1-argument
fns that could have been written with
#(... % ...), had I thought of it — which I didn't, because my pet language's equivalent only does partial application.
(alter r f x)is equivalent to
(alter r #(f % x)). This saves a little typing, but it's such an arbitrary convenience that I found myself worrying about whether it worked with each function's argument order.
- Clojure structures are dictionaries, but this isn't important; so far I've only used them like ordinary user-defined classes whose accessors happen to have names beginning with colons. It does mean they appear in a strange place in the documentation, though.
associs obvious in retrospect. I have wanted such an operator for structures, and I'd probably want it for dictionaries too, if I ever used nondestructive dictionaries.
- Some functions I missed:
intersection(they exist in
clojure.setbut not in the default namespace, and they don't work on lists),
member?(on values, not keys),
for-each(yeah, I know, I'm not supposed to want it), and a function that returns the first element of a list that satisfies a predicate.