I don't get it why Clojure and other Lisp tutorials throw things like this in face of beginners:<p><pre><code> (reduce + (map (comp inc inc) (range 10)))
</code></pre>
instead of just using things like the "->" macro to make the damn thing just as in any other language:<p><pre><code> (-> (range 10)
(map #(+ % 2))
(sum))
</code></pre>
...it's as if <i>they purposely try to scare people away from Lisp!</i> Some kind of smug superiority makes them wanna make sure no "lesser mortals" want to touch their beloved language. (The added benefit would be that later you can "blow their minds" but showing them how to <i>write "->" themselves</i> and instantly introduce them to the power of macros!)<p>This is almost close to showing a code snippet like this to someone trying to learn Haskell:<p><pre><code> fix$(<$>)<$>(:)<*>((<$>((:[{- thor's mother -}])<$>))(=<<)<$>(*)<$>(*2))$1
</code></pre>
(it only prints the powers of 2, but it was enough to scare some people into calling Haskell “the Taliban version of ML”... <a href="http://www.theregister.co.uk/Print/2012/12/21/financial_software_disasters/" rel="nofollow">http://www.theregister.co.uk/Print/2012/12/21/financial_soft...</a>)<p>...really, people, stop scaring learners away!
This is very similar to my experience. There's definitely some friction getting started; the odd using/require macros for importing (which switched sometime in the last 2 years, which means a lot of out-of-date SO answers); the fact that most docs recommend using emacs which, if you aren't already a user, is not going to be easy to deal with; the parens.<p>But it's amazing. I do really miss types, but found that unit tests + Schema[0] serves my needs; they provide some type-ish enforcement, and do a terrific job documenting my code so that when I return to it months later, I have some idea of what's in all these map structures.<p>Another unsung feature is that emacs + paredit + s-exprs + repl means the actual mechanics of writing Clojure is wildly better than any other dev environment. The ability to operate confidently and consistently on semantic units of code vs. text is really amazing and I immediately miss it when I switch back to JS or Python. I'm sure other Clojurists felt they had already experienced something like FB's Prune when it was discussed here a few days ago.<p>Not to mention learning a Lisp really does increase your understanding of what's possible. I've seen others recommend holding off on playing with macros until you've become quite familiar with the language, but I couldn't disagree more. Play with them right away! I have perhaps ~200 hours of Clojure under my belt -- barely anything -- but I've hacked together a few useful macros. Though I barely understand how they work when revisiting them, it definitely increased my understanding of what's possible, and what's missing, in other languages.<p>So yeah...learn some Clojure. Just do it.<p>[0] <a href="https://github.com/Prismatic/schema" rel="nofollow">https://github.com/Prismatic/schema</a>
I've asked on StackOverflow, got shut down violenty, so I try to ask here: how do developers in large(-ish) projects deal with the lack of tool assisted refactoring? (Renaming a field in Java, I am 100% sure that every call site is found, changed or otherwise the codebase does not compile. The catch-all clause of dynamically typed language users seems to be that the test suite should catch everything, but looking at most projects, it's outstanding to even see 40% test coverage.)
S-expressions have changed the way I think, and it's really great. With paredit (or some other structural editor) and hiccup[1] I can make html so quickly out of pure functions that it's truly a joy. Using Clojure's datastructures, along with all the functions made to alter those datastructures gives such an immense feeling of power.<p>[1] <a href="http://hiccup.space" rel="nofollow">http://hiccup.space</a>
As disclaimer, I've been a Scala zealot for the last 3 years. On Clojure vs Scala, usually comparisons are kind of unfair I think. As somebody interested in FP, I tried Clojure first. And FP being all about immutable data-structures, pure functions operating on immutable data-structures and dealing with side-effects explicitly by means of abstractions such as the I/O Monad, I expected to see that in Clojure, seeing how people describe it as being very FP.<p>And it was like a big WTF for me seeing how for example everything in Clojure is a Var that can be changed. And eventually I got it, I mean Clojure is a LISP and it's meant to be compiled by a REPL and the unit of compilation is not a file and this is in LISP's tradition and so on and so forth. But it's still a WTF given its usual characterization. Even in Java classes are completely immutable (until you start manipulating bytecode of course) and you've got "final" declarations. You have no "final" in Clojure. And this is actually a pity because it (probably) means that those persistent data-structures can't be implemented in Clojure, as "final" on top of the JVM comes with a pretty cool contract in the Java Memory Model that you need. And then instead of Scala's implicit parameters, that are actually very explicit and type-checked by the compiler, in Clojure you have package-wide vars or refs that people manipulate as a poor man's dependency injection mechanism. And state changes are usually done by mutating atoms. Well that's cool, in Scala people also do that, but in Scala you also have a mature implementation of the I/O monad and monad transformers if you want it, or various FRP libraries. And I'm sure that Clojure has plenty of libraries, but overall I feel that in Scala the ecosystem of design patterns <i>borrowed-from-Haskell</i> is more mature.<p>Scala provides mutable variants of its data-structures in the standard library. But that's not extra rope to hang yourself, because in Clojure the use cases still exist and people end up using Java's mutable data-structures instead. In Scala the blend of OOP and FP can sound overwhelming, but it's actually the healthiest blend of FP + OOP to ever come up in a programming language. And in Clojure, just because you can't declare a class, that doesn't mean that OOP doesn't exist. It does and the blend is not healthy. This is why "reify" and "proxy" exist and are used quite frequently. And things like clojure.lang.ISeq are a Java OOP interface and not a protocol. Basically Scala is trying to make the best of OOP, whereas Clojure considers it a necessary evil of the host platform that it can't get rid of. Pick your poison.
> I can refactor a huge codebase in my IDE. Good luck trying that with Clojure.<p>Cursive Clojure, a plugin for IntelliJ has excellent refactoring support, especially considering how dynamic Clojure is. If you've come from a Java background, this gives you a lot of the refactoring and 'Where Used' functionality that you might be used to.
Great post.<p>Most discussions on lispy languages focus on the syntax. The unsaid assumption is that the only diff between a lisp and a non-lisp is the syntax. However, using lisp actually makes you think differently about programming. It is that which for me personally, is the selling point of a lisp. The syntax itself is merely expression, and is honestly simpler to grok than most other languages thanks to its consistency.
This post focuses on Clojure as a very pragmatic Lisp, but that, while great in itself, is not what makes Clojure so interesting. Clojure has proposed an interesting solution to the shared mutable state problem without trying to reduce it as much as possible like Erlang and without being pure functional -- and thus changing the whole concept of state and effects -- like Haskell (Clojure is imperative-functional). Clojure's solution is that all shared mutable state be transactional (or atomic, which is a special case of transactional). AFAIK, this is the first language to do that (again, without going down the pure-functional path). An imperative language that doesn't restrict global mutable state, but makes it transactional is a very interesting case study.
I wonder why the author left the thread-first and thread-last macros as a footnote.<p>This:<p><pre><code> (reduce + (map (comp inc inc) (range 10)))
</code></pre>
And this:<p><pre><code> (->>
(range 10)
(map (comp inc inc))
(reduce +))
</code></pre>
Are totally different from a readability standpoint. Yeah, there's some initial syntax to get used to, but in the long run I've noticed it to be the difference between immediately understanding what some code is doing and having my eyes glaze over.
For the OP - i've recently been programming in Scala, mostly spark jobs, nothing too complex, but damn its beautiful. I love how expressive the language is (certainly it can also be very complicated in large codebases, as you use more features, but I haven't encountered that yet). Heck just the simple 'scala' repl + JVM interop makes developing Java code so much easier. You seem to have tried Scala too - i'm wondering what you think of Clojure vs. Scala and what made you switch?
<p><pre><code> (reduce + (map (comp inc inc) (range 10)))
</code></pre>
is not analogous to (_ + 2). The more similar and idiomatic code is:<p><pre><code> (reduce + (map #(+ % 2) (range 10)))</code></pre>
The first example could also be written as:<p><pre><code> (reduce + (map #(+ % 2) (range 10)))
</code></pre>
which looks more like the Scala example, or, perhaps more readable, in the long form:<p><pre><code> (reduce + (map (fn [x] (+ x 2)) (range 10)))</code></pre>
Why is reading from left to right counter-intuitive? Don't we read f(x, y) from left to right as well? (f x y) has the same order, even the same amount of parentheses.
For this code,<p>(reduce + (map (comp inc inc) (range 10)))<p>Clojure people would normally use thread macro, like so:<p>(->> (range 10) (map #(+ % 2)) (reduce +))<p>This would be similar to the Scala version in term of order.
> <i>The use of s-expressions and their resulting prefix notation is in many respects the secret sauce of Clojure. However, it requires you to read right-to-left3 and inside-out. This is counterintuitive to most of us, and takes some getting over. It is a very real barrier to entry for Lisps.</i><p>Author still doesn't get it. Lisp must, in fact, be read from the outside in, left to right --- in the same obvious direction in which it is evaluated. It is not very different from a nested f(x, y, ...) notation, except that the commas are gone, and the f ( exchange position to the ( f arrangement.<p>As a trivial example of why outside-in is wrong, is that if you follow that order, you might believe that (b c) is a function call in (defclass a () (b c)). The most important fact is that this is a class definition (in ANSI Common Lisp). The leftmost symbol in the outermost form, defclass, is the what McCarthy called the "main connective". It determines the entire meaning of the interior of the form and all of its subforms, so it behooves us to visit that first. When we recognize that, we then know that (b c) are slots, and not operator b applied to argument c.<p>The right-to-left part is completely bizarre. Why, as a matter of course, would you read the arguments of a form before the main connective symbol? If that, you want, languages for you, there are: Forth, PostScript.
As much as I like Clojure the lack of static typing always made refactoring a pain. The lack of it makes this a beauty I just can't appreciate and see.
My couple of comments:<p>- Great write up, very approachable for who may be on the fence about toying clojure.<p>- Re: Enterprise, while our enterprise footprint isn't ubiquitous yet, there are a couple of big name companies using clojure pretty heavily. Amazon, Walmart and Two Sigma amongst them. Also, because clojure sits on the JVM, we have a good interop story with legacy java codebases.<p>- Re: Refactoring, you should checkout intellij + cursive clojure, it's made refactoring codebases much easier for myself.
I would like to try to use Clojure for mobile development.<p>Together with ClojureCLR, RoboVM and reader conditionals it would be a good mix, without having to deal with FFI headaches.<p>But currently it is too slow on Android and I don't know how well ClojureCLR, if at all, runs as Universal Windows App.
Not, entirely true, there are some Clojure Frameworks like this one <a href="http://coils.cc/coils/a-framework.html" rel="nofollow">http://coils.cc/coils/a-framework.html</a>