19 ms·
The Beauty of Clojure
- bonobo3000 11y agoFor 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?
- bad_user 11y agoI've been a Scala developer for the last 3 years and I'm in the camp that first played with Clojure and then switched to Scala. I'm still playing with Clojure from time to time, but I like Scala more. The only difference one should care about is static typing versus dynamic typing and that's it. Of course it's not a binary black and white classification when looking at languages, for example Scala is less static than Haskell and Clojure is less dynamic than Ruby. But overall that's the biggest difference and that changes the style, the mentality you have, how the code looks like. As an example you are much more likely to see in Scala the Maybe/Option monad, whereas in Clojure people prefer to work with null (nil). Rich Hickey said it himself that Option[T] only makes sense in a static language. In general in Scala you're more likely to see things like monads and functors being modeled explicitly. It's more natural to do that in Scala because it's all a matter of describing functions with certain signatures. And you know, when working with combinations of flatMap and map, it's much easier if you have the compiler telling you where you've fucked up. Which is why many people do use things like Scalaz or Cats or Shapeless. On the other hand in Clojure it's much easier to build things like transducers, because there's no type-system to stay in your way. Clojure people work with lots of DSLs built over macros. You can see that in how Clojure exposes those channels in core.async and compare that to how asynchronous processing of events is done in Scala (e.g. Future, Iteratees, scalaz-stream, Rx, Akka actors, Reactive Streams, shameless plug: https://github.com/monifu/monifu https://github.com/monifu/monifu). Of course, Scala has its own macros system, but like in other static languages, the macros in Scala are only being used by those that really know what they are doing. But because of that, in both languages you get features exposed as libraries instead of being special language features. For example here's Scala's equivalent for C#'s await/async exposed as a library: https://github.com/scala/async https://github.com/scala/async Which one is better is actually dependent on the problems you're trying to solve. The problems I'm working on have a very complex business logic and are a better mix for static typing. This is why people will never agree on which one is better, because it actually depends on context.
- sklogic 11y agoScala got no macros. Which makes it infinitely less powerful than any language with macros, including even something like Clojure.
- bonobo3000 11y agoSimilarly, Clojure got no types. Which makes it infinitely less runtime-safe then any language with types. Takes a more nuanced argument.
- sklogic 11y agoFor a language with macros it does not matter at all. You can add any type system you like on top of it. Metalanguages are infinitely flexible.
- justthistime_ 11y agoThe strength of a type system is that there is one. If everyone uses his/her own self-made pet of a typesystem most of the benefits are gone as soon as you are more than a one-man-hobbyist project. Probably explains why most Lisps consist of one-man-hobbyist projects, though.
- sklogic 11y ago> The strength of a type system is that there is one. I do not agree. There is no single type system that would fit all the possible use cases. > Probably explains why most Lisps consist of one-man-hobbyist projects, though. No, it does not.
- justthistime_ 11y agoType systems are by-definition only a conservative approximation of all programs. The good thing is that 99.999999999999% of all programs never need to be written, and there are plenty of type systems which deal well with the last 0.00000000001. > No, it does not. Ok, that leaves the failure of Lisp outside of hobbyist mom's basements a mystery.
- rjeli 11y ago(reduce + (map (comp inc inc) (range 10))) is not analogous to (_ + 2). The more similar and idiomatic code is: (reduce + (map #(+ % 2) (range 10)))
- pka 11y agoGreat! Now go for Haskell :)
- jdeisenberg 11y agoThe first example could also be written as: (reduce + (map #(+ % 2) (range 10))) which looks more like the Scala example, or, perhaps more readable, in the long form: (reduce + (map (fn [x] (+ x 2)) (range 10)))
- edgyswingset 11y agoI wonder why the author left the thread-first and thread-last macros as a footnote. This: (reduce + (map (comp inc inc) (range 10))) And this: (->> (range 10) (map (comp inc inc)) (reduce +)) 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.
- huahaiy 11y agoFor this code, (reduce + (map (comp inc inc) (range 10))) Clojure people would normally use thread macro, like so: (->> (range 10) (map #(+ % 2)) (reduce +)) This would be similar to the Scala version in term of order.
- fnordsensei 11y agoThis reminds me that there really should be a reader macro for "partial" in Clojure.
- numlocked 11y agoThis 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. 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. 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. 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. So yeah...learn some Clojure. Just do it. [0] https://github.com/Prismatic/schema https://github.com/Prismatic/schema
- deleted 11y ago[deleted]
- Mikera 11y agoNote: you don't need to commit to Emacs to get the productivity benefits of Clojure. Eclipse with the Counterclockwise plugin (and I believe IntelliJ with Cursive too) also give you comprehensive REPL, s-expr manipulation and paredit features.
- huahaiy 11y agovim with fireplace.vim and paredit.vim plugins are pretty nice as well.
- escherize 11y agoS-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. [1] http://hiccup.space http://hiccup.space
- pixie_ 11y agoThe line below looks a lot more readable than the closure version. Enumerable.Range(1, 10).Select(i => i + 2).Sum()
- huahaiy 11y agoIt depends on whom you ask. As a person who doesn't even know what language this is written in, I don't think it is readable at all. For example, this "Select(i => i + 2)" piece looks totally strange for me. What does it do? After learned Lisp, I feel there's too much syntax to learn for non-lisp languages. Every language introduces some idiosyncratic syntax. Life is too short, I just don't want to learn syntax that someone else invented to just be able to say "this is different".
- sheepmullet 11y agoIn clojure you would typically write it as: (->> (range 10) (map #(+ % 2)) (reduce +)) Which is just as readable as the .net equivalent.
- Scarblac 11y agoAnd I prefer sum(i + 2 for i in range(1, 10)) for readability. But we shouldn't judge languages on this sort of thing -- because then why don't we just write sum(range(3, 12)), or 63?
- pixie_ 11y agoIn both those cases you have to read it backwards to see what it does.
- _pmf_ 11y agoI'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.)
- jarcane 11y agoI've never really needed it in Clojure. The code is that much more expressive that overall LoC is dramatically reduced (thus more easily viewed "in the whole"), and following a proper functional approach means good functions should be largely interchangeable and not dependent on any global state. So on the rare occasion I need to move something around, it's just meant a quick copy and paste and tweaking a few references. Yes, Clojure is dynamically typed, but I think you're missing the gains you get from FP and expressiveness in terms of reducing labor.
- eurekin 11y agoI was trying to find time for Clojure, but I had always had doubts - exactly as the grandparent commenter... He touched upon what I think is essential. The biggest things to lose by giving up Java is the tooling. Using the IDE I have many possibilities to learn about code, without ever needing to run it. For example by using find usages/implementations. The types on methods often give out hints about the big picture. Like, if a method passes a JAXB context, than I know I'm in the infrastructure layer. If I'm looking for a business rule "how is the price calculated", I can very quickly cut off a large portion of code which does not do it. After few iterations, it's almost certain I will get to the relevant code part. Then I typically launch an application and put a breakpoint, to test my assumptions. After that, I have a lot of confidence about how the application behaves in that spot, even If I saw it for the first time. If anybody asks me "Tell me if we can extend the functionality of calculating user score to include expert rating", I can provide a good effort approximation, even in a never seen before application. I just know, that given the tooling, I can filter out unrelated code parts and concentrate only on the relevant one. The above concerns only code comprehension of a large project. The tooling does give you also power to do macro level restructuring, like breaking a large monolith into smaller modules. The compile time errors are like a high accuracy automatically generated todo-list. Since it takes so little effort to make large changes, you gain the ability to do it multiple times and refine. If I broke something into two modules and a dependency cycle came up, I see it right away and can do everything again, a little bit better with the extra knowledge I got. I have never seen anybody doing the same in Ruby, maybe because I've been working only about two years commercially in it, but a lot of times I've seen something quite opposite: introducing more hacks decreasing code quality, just to work around some conceptual error. While working in Java, I have seen a lot of situations like "we'll doing a hack here and the real solution will come with the next big release" and really the solution does come. High level changes typically break a lot. With tooling, you gain confidence you can fix them, without introducing a lot of regression bugs. In Ruby, I saw the people just give up and leave monkey patches around. Then they just leave the project for the next unfortunate programmer. So, back to the topic. You say that the change name refactoring is very simple in Clojure, because the impact of change is local to the method. My question is: How does Clojure tooling aid You in changing a place with a global impact? Like, changing the argument list of a very often called method (e.g. the method to serialize a structure to string to require the caller to provide an explicit encoding)? Can You, right after doing the change, quickly estimate how much work is there to fix all affected places? Do You have confidence that the list you provide is complete and doesn't miss anything critical? How certain can you be without running the code, only analyzing?
- dantiberian 11y ago> I can refactor a huge codebase in my IDE. Good luck trying that with Clojure. 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.
- lewisl9029 11y agoYep, Cursive is definitely quite impressive. Here's a very informative intro by Cursive's developer, complete with some very compelling demos: https://www.youtube.com/watch?v=vt1y2FbWQMg https://www.youtube.com/watch?v=vt1y2FbWQMg
- diego 11y ago(comp inc inc) is weird. You'd normally use #(+ 2 %) or (partial + 2) instead.
- zeekay 11y ago(reduce + (map (partial + 2) (range 10))) ...reads great in my opinion. Surprised no one else has brought it up.
- owen4d 11y agoYeah, definitely. My bad. It's definitely not idiomatic. I think was trying to avoid using the anonymous fn syntax, as I thought that was be more off-putting, and partial hadn't occurred to me at the time. In the end, I think I just confused the issue. In retrospect, I should probably have used a totally different example, and avoided map and reduce as they are FP idioms. Live and learn, as they say.
- nnq 11y agoI don't get it why Clojure and other Lisp tutorials throw things like this in face of beginners: (reduce + (map (comp inc inc) (range 10))) instead of just using things like the "->" macro to make the damn thing just as in any other language: (-> (range 10) (map #(+ % 2)) (sum)) ...it's as if they purposely try to scare people away from Lisp! 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 write "->" themselves and instantly introduce them to the power of macros!) This is almost close to showing a code snippet like this to someone trying to learn Haskell: fix$(<$>)<$>(:)<*>((<$>((:[{- thor's mother -}])<$>))(=<<)<$>(*)<$>(*2))$1 (it only prints the powers of 2, but it was enough to scare some people into calling Haskell “the Taliban version of ML”... http://www.theregister.co.uk/Print/2012/12/21/financial_software_disasters/ http://www.theregister.co.uk/Print/2012/12/21/financial_soft...) ...really, people, stop scaring learners away!
- agumonkey 11y agoFirst principles I guess, although I sure love `->` and its siblings.
- nnq 11y agoIf you start thinking along the lines of first principles you could just as well start with a NAND gate. ..because some people were even smart enough to pull this off: http://www.nand2tetris.org/ http://www.nand2tetris.org/ :)
- agumonkey 11y agoRecursive first principles. I don't think n2t will put the emphasis on that (no offense, I love their work and results, something I wished for years).
- kaonashi 11y agoMcCarthy had something to say about this... http://www-formal.stanford.edu/jmc/recursive.pdf http://www-formal.stanford.edu/jmc/recursive.pdf
- bad_user 11y agoAs 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. 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 borrowed-from-Haskell is more mature. 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.
- pjmlp 11y ago> 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. Have you tried OCaml or F#?
- crimsonalucard 11y agoWhich is more beautiful, haskell or clojure? This is a subjective question, so I'm hoping to read subjective and highly opinionated answers.
- rtpg 11y agowell that first example in the article would be foldl (+) 0 (map (+2) [1..10]) or sum $ map (+2) [1..10] So... make up your own mind. As someone who has written only small chunks of lisp but at many different points in life, I do find the whole parens game to be a bit disorienting, but usually I can counter it by using newlines. I would write something like (reduce + (map (comp inc inc) (range 10))) as (reduce + (map (comp inc inc) (range 10)))
- crimsonalucard 11y agoI deal with it by using an IDE or plugin for paren highlighting.
- zeekay 11y agoRanges are inclusive, so: sum $ map (+2) [0..9] Nitpick, but it confused me for a second.
- hellofunk 11y agoThey are both beautiful in their own way. Haskell is more pure, certainly, and closer to mathematical beauty, definitely. But the extraordinary flexibility of a lisp, which Clojure does immensely well, makes it more powerful than any language I've experienced for quickly expressing or manipulating an idea. Any program written in Clojure could eventually be refactored into another language, but its strength as a dynamic lisp makes it possible to really get to the heart of a problem and work out its solution without anything else getting in the way.
- daxfohl 11y agoHaskell was created to be beautiful. Clojure was created to be practical, with plenty of ugly pragmatism thrown in. If there's an argument over beauty, it should be Haskell vs Lisp (vs Prolog, vs Forth, and probably vs assembly).
- pjmlp 11y agoI would like to try to use Clojure for mobile development. Together with ClojureCLR, RoboVM and reader conditionals it would be a good mix, without having to deal with FFI headaches. But currently it is too slow on Android and I don't know how well ClojureCLR, if at all, runs as Universal Windows App.
- hellofunk 11y agoFor mobile a lot of folks are now using Clojurescript. There have been several highly rated discussions on here about the breakthroughs in the Clojurescript community for getting it to operate very quickly on mobile devices.
- pjmlp 11y agoThanks, but that approach is not something I would enjoy. I try to stay away from the JavaScript universe unless I have a customer requesting a webdev project. For me mobile dev means only native code and not web widgets.
- mateuszf 11y agoClojureScript + React Native would produce a native app.
- pjmlp 11y agoNo because they run on a JavaScript container of some sort.
- hellofunk 11y agoThat's not entirely true. React Native does indeed run on the native device, not in a JS "container". The JS part is a bit of glue code. In the case of iOS for example, React Native uses the same exact UI toolkit that runs natively on the iPhone as if you have written it in Swift or Objective-C. Apple's JavaScriptCore exposes the native elements with a JS api, but they are not running inside JS. Only a minor part of the glue is interpreted as JS, and it's not usually the CPU-intensive stuff.
- sanatgersappa 11y agoGreat post. 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.
- skimpycompiler 11y agoAs 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.
- cranium 11y agoPerfect description of the average Enterprise codebase!
- tragic 11y ago"FYI - there are better books to get started with [than the Joy of Clojure]." FMI: what are they? :-)
- emil0r 11y agoProgramming Clojure 2nd ed, Clojure for the Brave and True. Probably others as well :)
- erichmond 11y ago+1 for clojure for the brave and true. Best overview book out there IMO.
- akilism 11y agoLiving Clojure is really good also.
- owen4d 11y agoYeah, that footnote could have been more helpful! I've replied to a comment on the post with my thoughts on books. Hope that helps.
- infiniteseeker 11y agoIs there a good online course for a beginning programmer who wants to learn Clojure? If not, a good book with exercises?
- ianox 11y agoI saw http://www.4clojure.com/ http://www.4clojure.com/ mentioned elsewhere in this thread. That'd be as good a place as any to start.
- mateuszf 11y agohttp://www.braveclojure.com/ http://www.braveclojure.com/ is pretty good read.
- owen4d 11y agoClojure Koans, is similar to 4clojure, and also worth a look> http://clojurekoans.com/ http://clojurekoans.com/ Bookwise, I thought the following were pretty good: 'Clojure in Action' by Amit Rathore and 'Clojure Programming' by Chas Emerick are both solid intro Clojure books. 'Web Development with Clojure' by Dmitri Sotnikov is also a great book, which obviously comes with more of a web-slant. And I'd give a second thumbs up for 'Clojure for the Brave and True'.
- pron 11y agoThis 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.
- mseepgood 11y agoWhy 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.
- daxfohl 11y ago(< x y) is the main one I have trouble with. The alligator is eating them both!
- erichmond 11y agoMy couple of comments: - Great write up, very approachable for who may be on the fence about toying clojure. - 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. - Re: Refactoring, you should checkout intellij + cursive clojure, it's made refactoring codebases much easier for myself.
- erichmond 11y agoAlso, anyone interested in seeing what a productive clojure workflow looks like should check out this screencast http://www.parens-of-the-dead.com/e1.html http://www.parens-of-the-dead.com/e1.html It's definitely "jaw on the floor" worthy, if you haven't seen something like this before.
- owen4d 11y agoThanks. Re: Enterprise, I've seen a presentation about some of the work going on at Walmart. Would be great to hear more 'stories from the trenches' from larger scale developments. It would really help sell the language. Re: Refactoring, yeah, I'm totally busted on that one, my wording was way too strong as I haven't tried either Cursive or clj-refactor. Plus in a way, it's an apples and pears comparison, between OO and FP refactoring.
- zubairq 11y agoNot, entirely true, there are some Clojure Frameworks like this one http://coils.cc/coils/a-framework.html http://coils.cc/coils/a-framework.html
- kazinator 11y ago> 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. 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. 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. 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.
- owen4d 11y agoHa, yeah, maybe. I guess what I meant is this, say you have some OO/Scala-like code like this: mydata.doA(blah).doB().doC(blah2).etc() You can read it left-to-right: I've got some data, I do something to it, I then do something else to that result, etc. Maybe it's just that I've been using Java, and the like, for years but this feels simpler to me than s-expressions. At least initially. With s-expressions I get something like (ignoring threading macros): (etc (doC blah2 (doB (doA blah mydata)))) So when reading, from a data perspective, you kind of read outwards from the inner s-expression (which seems right-to-left to me). Or keep a kind of mental stack, as you read inwards (left-to-right). No?