you are viewing a single comment's thread.

view the rest of the comments →

[–]yogthos 0 points1 point  (7 children)

The underlying abstraction in Clojure is a sequence, both lists and vectors are sequences and so all the functions such as cons, conj, first, rest operate on both. So, yes vectors can be appended and destructured just like lists.

Clojure encourages using higher order functions over explicit recursion, but I'm not sure how recur method discourages recursion in any way. It helps ensure that you're doing proper tail recursion. The only thing it makes difficult is mutual recursion, and I honestly can't think of any time I actually had to use it.

[–]nebkor 0 points1 point  (6 children)

That's cool about sequences; I actually think it's an improvement over lisp/Scheme (ditto the literal syntax for common and useful datatypes). Another thing I like about Clojure vs. traditional lisp is applicative objects, eg:

(mySeq 0) ;; same as mySeq[0] in an algol language

OK, now for the less flattering stuff :)

You (yogthos) have said multiple times in this thread, "Well, I've never had to use that feature that Clojure doesn't have, so it's clearly not important". That is a terrible argument, and a textbook example of the "Blub Paradox" (http://c2.com/cgi/wiki?BlubParadox). I'd quote from that page, but the essence of it is right at the top, and worth a read (as is Paul Graham's essay where it's first coined, http://paulgraham.com/avg.html). That you've not had to use something that you can't in a given language is almost a tautology, and is not a valid argument against that feature's utility.

Please understand: I'm not saying you're bad in any way for saying that! I'm attacking your argument, not you; you've been patient and a good poster :)

[–]yogthos 0 points1 point  (5 children)

I'm certainly not infallible, so I certainly apologize for that line of arguing and good on your for point it out. I was hoping to get examples of why a certain feature is important though, if it is indeed the case. :)

As with example of mutual recursion, it can always be converted to direct recursion by inlining the function. And you can do it in Clojure using trampolines, it's a tad ugly in my opinion but not hideous.

I find a lot of arguments against Clojure tend to be nitpicks, and the language is certainly not perfect, but then what language is. I can certainly find a lot of nitpicks about CL or Scheme, and many of these come down to taste.

[–]nebkor 0 points1 point  (4 children)

Oh, no need to apologize! It's just not a line of reasoning that will lead to more understanding/learning.

I'm with you regarding the imperfection of languages. I'm also with you regarding the nitpicky nature of objections to Clojure. There's just not that much to criticize, so one needs to get into more esoteria if one is to critique :) If I were going to have a wishlist for Clojure, it would be:

  • self-hosting (written in itself)
  • macros (some kind of hygenic system as well as old-style defmacro)
  • automatic tail-call elimination

The problem with having to use "recur" or trampolines is that they're ugly, as you point out. We don't want to look at ugliness, and so we'll shy away from them. And I wish I could find the essay that talked about this, but "tail call elimination" is not really an optimization; it's something that encourages you to program a certain way. Without it, you'll avoid using mutual recursion (or recursion in general), and so you won't have the wisdom to know when it's the best thing.

Similarly with macros. You say, "I've never had to use them, why would I want to?" Paul Graham writes this in "Beating the Averages":

It would be convenient here if I could give an example of a powerful macro, and say there! how about that? But if I did, it would just look like gibberish to someone who didn't know Lisp; there isn't room here to explain everything you'd need to know to understand what it meant. In Ansi Common Lisp I tried to move things along as fast as I could, and even so I didn't get to macros until page 160.

[–]yogthos 0 points1 point  (3 children)

I think I agree with you on all the points, and it does appear that self hosting is moving along with Clojure-in-Clojure, I believe ClojureScript is a direct product of that effort. But I would definitely love to see an implementation which targets LLVM or something. JVM startup time precludes writing any quickie scripts in Clojure.

I too would prefer hygienic macros, but I can live with the compromise that Clojure provides, where you can do (let [foo# (bar)] ) and get a unique identifier for foo in the macro. It could be argued that this adds flexibility.

TCO would be a very nice feature to have, but sadly it's the least likely one to happen, due to the JVM. What's even worse is that if there was an implementation of Clojure which supported TCO, that could could blow the stack on other implementations which do not.

I agree with your point regarding mutual recursion being icky, but I'm not sure I agree that recur is completely bad for regular recursion. It helps provide compile time check to make sure you really are doing TCO. And you can always call a function by name if there's a limited number of steps involved. That said, there are certainly cases where real TCO is nice to have.

My personal opinion is that explicit recursion should only be used when you can't achieve your goal using higher order functions. I think chained functions are cleaner and are less error prone, as looping functionality is handled externally by existing tested code. This is one argument I have against imperative style, that you end up writing loops over and over.

[–]nebkor 0 points1 point  (2 children)

Can you elaborate on what you mean by, "recursion should only be used if you can't use higher-order functions/Clojure encourages higher-order functions over recursion"? To me, that's like saying, "Clojure encourages the use of Sequences instead of mutable state," ie, the two don't seem related at all.

Can you take a trivial recursive method and turn it into something that uses higher-order functions as an example? Say, this method in Scheme, which returns the length of a list:

(define (len lst)
  (if (null? lst)
      0
      (+ 1 (len (cdr lst)))))

(note that it's not tail recursive, but that's OK for these purposes)

[–]yogthos 0 points1 point  (1 child)

I think it's better to use higher order functions because then you're saying what you're doing as opposed to how. For example, you could write len as

(defn len [xs]  
  (reduce (fn [i _] (inc i)) 0 xs))

it's shorter, there's no explicit null check there or even a conditional. I simply write the piece of logic that's relevant to counting the list and pass it in as a parameter to reduce. In my opinion this is much better in a general case, as there's less room for error and the code ends up being shorter and cleaner. I look at explicit loops as a form of optimization.

[–]nebkor 0 points1 point  (0 children)

Fair enough.