Hacker News new | past | comments | ask | show | jobs | submit login
Critiquing Clojure (sjbach.com)
60 points by sjbach on June 22, 2009 | hide | past | favorite | 11 comments

A lot of these are personal preference and borderline traditionalist, and as pointed out by others, the dynamic binding one is in fact not strictly true.

One thing that does however bother me about Clojure is the interaction of dynamic binding and lazy evaluation. Allow me to illustrate:

  => (def *test* 0)
  => (binding [*test* 2]
       (map #(* % *test*) '(1 2 3)))
  (0 0 0)
The call to map is lazy, and the returned seq is only evaluated once it hits the 'P' of REPL, which is outside the dynamic binding of test. If you surround the map call with (doall and ), the result is (2 4 6).

This is of course a trivial case, but I've been bitten by this in practice where the binding, lazy sequence and var evaluation were a few call steps apart, and it's always been very unpleasant. Most of the time, it's involved vars which don't have a root binding, so it throws an error, but in other cases it's a lot more serious.

I guess the same problem can be provoked in both Clojure and CL by returning a closure evaluating that var to back outside the dynamic binding, but since you'd be explicitly invoking the closure, it's a lot more obvious what's going to happen than the "magic" that is lazy evaluation.

I was interested in Clojure before, but the critisism I found in the two recent posts here on HN make it even more interesting. I feared something substantial might turn up that really defies using it, but to the contrary: if issues listed in the blog posts are the biggest problems people find with Clojure, then that is quite promising.

I find the mention of obscure debug information where most of the time you can't find line number of Clojure code where the error occurred --- as a big problem.

I don't have much experience in CL, but I started to really like the fact that clojure is Lisp 1. When I have a hash map "client", I can use all three:

(get client :phone)

(:phone client)

(client :phone)

It takes some getting used to, but I already came to expect (and enjoy) that any symbol I use can be a function.

in cl, you can use defsetf to almost do the same. as a matter of fact, taking a cue from arc, you can write a macro like:

  (defmacro deftable (name)
       (defparameter ,name (make-hash-table))
       (defun ,name (k)
         (gethash k ,name))
       (defsetf ,name (k) (v)
         `(setf (gethash ,k ,',name) ,v))))
now you can do: (deftable client), which creates the hashtable, and then

(client 'phone) will retrieve the value,

(setf (client 'phone) '4444444) will set the value.

I also think that reading lots of "funcall"s makes the intent of the code less readily apparent.

it is definitely more verbose and uglier. but in practice it does not seem to be a problem. for instance, comparing to arc, if you had notfn for ~, and testify, you could write all as:

  (defun all (test seq)
    (funcall (notfn #'some) (complement (testify test)) seq))
doesn't seem that bad.

On #5, the code isn't doing what the author may think:

     (let [#^int a 2
           #^int b 3]
       (cons a b))
a and b are not hinted to be primitive types. Type hinting is purely for objects. Use hinting to make it clear to the compiler what sort of objects you are passing into something (e.g. a constructor that takes a String). If there is confusion, clojure will do reflection.

The right thing to do in this case, is the second thing that the author mentions:

   (let [a (int 2)
         b (int 3)]
     (cons a b))
a way to see that a and b are really primitive is:

    (let [x (int 5)]
      (.shortValue #^Integer x)) ; exception: "can't type hint primitive..."
There's a post on the mailing list that goes over this much better than I did (and where I drew the examples): http://groups.google.com/group/clojure/browse_thread/thread/...

I understand that this is really beside the point, that the author finds type hinting syntax unattractive, but I just wanted to clear up confusion (if there was any).

EDIT: clarity

I'm not familiar with CL's "PROGV", but you can indeed dynamically rebind vars at runtime with Clojure: http://paste.lisp.org/display/73551

#2, "Functions must be declared before first call",is very irritating (to me) It feels like I am back in Pascal land

It also eliminates a very large class of errors- typos when referencing other functions. Tradeoffs...

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact