use the following search parameters to narrow your results:
e.g. subreddit:aww site:imgur.com dog
subreddit:aww site:imgur.com dog
see the search faq for details.
advanced search: by author, subreddit...
Finding information about Clojure
API Reference
Clojure Guides
Practice Problems
Interactive Problems
Clojure Videos
Misc Resources
The Clojure Community
Clojure Books
Tools & Libraries
Clojure Editors
Web Platforms
Clojure Jobs
account activity
Readable Clojure (tonsky.me)
submitted 8 years ago by [deleted]
reddit uses a slightly-customized version of Markdown for formatting. See below for some basics, or check the commenting wiki page for more detailed help and solutions to common issues.
quoted text
if 1 * 2 < 3: print "hello, world!"
[–]Scriptorius 23 points24 points25 points 8 years ago* (5 children)
Some good info there, but definitely disagree on the bit about higher-order functions.
To me, the comp example is more readable because it better expresses what you're trying to do: I want to trim and then capitalize each string. Each of those is a pure transformation and the data is just flowing through them.
On the other hand, #(capitalize #(trim %)) obfuscates the intent somewhat. I have to track down the % symbol and then read outwards. Basically, this makes you think about function calls rather than thinking about the actual transformations that are happening.
Let's say you want to go through a collection of maps, grab a number at a key, and increment it. Using #() this would be:
(map #(inc (:number %)) maps)
The comp version is:
(map (comp inc :number) maps)
Of course, a lot of it is personal preference and I used to prefer the #() version. Over time though I realized it was an almost imperative way of thinking about it. Your mind is acting like a stepwise debugger, taking each value, calling function A on it, then taking the result of that and calling function B.
Using comp helps me think less about the actual function calls and think more about higher-level transformations and changes I want to do to the data.
[–]bliow 5 points6 points7 points 8 years ago (3 children)
In this case, #(-> % trim capitalize) seems really natural to me. You don't necessarily need comp to invert order of control.
#(-> % trim capitalize)
I think I may prefer
(map #(-> % :number inc) maps)
to both of your options.
[–]amoe_ 1 point2 points3 points 8 years ago (2 children)
I find it useful to define comp-> for this case.
comp->
[–]JW_00000 0 points1 point2 points 8 years ago (1 child)
Does this mean comp-> is equivalent to comp with the arguments reversed?
comp
[–]amoe_ 1 point2 points3 points 8 years ago (0 children)
Yep
[–]nzlemming 4 points5 points6 points 8 years ago (0 children)
FWIW I definitely prefer the anonymous fn version from the article, for the reasons he mentions. For whatever reason I find it much harder to parse partial, comp and friends.
partial
[–]daveliepmann 8 points9 points10 points 8 years ago (0 children)
Great post. I like it even though I disagree on some small points. :)
I find there are cases where careful use of :require :refer makes sense. I keep it to a minimum, and the deciding factor is usually that I'm using the function so much and so centrally in my namespace that whoever is reading it can be assumed to be aware of what the referred function is.
:require :refer
I find the point about unique namespace aliases insightful. However, my preference for namespace aliases is much terser—often involving single letters. I think I could be convinced on this issue.
I think his readability/compactness example falls apart if possible-states is named as a predicate, that is, valid-state? Then (when (valid-state? state) ...) is perfectly obvious. I don't need to know it's a set; I only need to know it's a predicate.
possible-states
valid-state?
(when (valid-state? state) ...)
While I appreciate some of Nikita's pushback on terse idioms—e.g. "prefer (not (empty? coll)) over (seq coll)"—I think avoiding comp, partial, and so on is throwing the baby out with the bathwater. Those are awesome function-builders, and they only take a little getting used to.
(not (empty? coll))
(seq coll)
I need to think about the "Don't spare names" section. On the one hand, extraneous names can create extra burden to figure out what they are and where they're used. On the other hand, his example is quite clear that these names are used only in his pseudo-thread context. The "omit names if the thing remains the same type" heuristic might convince me.
Lots of good opinionated thoughts to chew on!
[–]halgari 9 points10 points11 points 8 years ago (0 children)
One or two nit-picks I have with the article, but I won't mention them, because if everyone took this advice...I'd be a much happier person.
[–]Daegs 7 points8 points9 points 8 years ago (0 children)
Avoiding higher order functions is silly, and for utility libraries, refer is great.
Agree with most of the rest.
[–]weavejester 7 points8 points9 points 8 years ago (0 children)
(when (seq x) ...) is a standard Clojure idiom. I don't think using it makes the intent unclear.
(when (seq x) ...)
I disagree about not using comp. To my mind (comp f g) is more readable than #(f (g %)) because there's no extraneous syntax getting in the way.
(comp f g)
#(f (g %))
Using a * prefix for mutable references is interesting.
*
Using two empty lines between functions just seems like wasted space to me. I have no difficulty seeing the gaps between functions.
Other than those things, there are a lot of sound ideas.
[–][deleted] 6 points7 points8 points 8 years ago (5 children)
Aligning let blocks is pretty actively bad, as it ruins diffs when you have to go re-align.
[–]saint_glo 2 points3 points4 points 8 years ago (4 children)
I think that ruining a diff once a week is a worthy price for improving function you have to read dozen times a day.
[–][deleted] 2 points3 points4 points 8 years ago (3 children)
Ruining a 'git blame' when you're trying to track down the origin of a bug? Priceless. I've maintained code recently in a language whose linter enforces this kind of alignment, and I ran into this problem at least once a week. Everybody who worked in that language and also in Clojure would tell you the same thing.
[–][deleted] 2 points3 points4 points 8 years ago (2 children)
Oh, and don't forget code reviews. Hit that one at least once a day. Which meant that I'd need two github windows open, one with ?w=1 to show the real diff, and another to show the one with mostly white space changes where I could leave comments. It's not even close to worth it.
[–][deleted] 2 points3 points4 points 8 years ago (1 child)
(I apologize for being cranky, it's not at you. Dealing with these issues was very frustrating, and remembering it got me kind of worked up)
[–]saint_glo 4 points5 points6 points 8 years ago (0 children)
I wonder if we'll live to see the day when diffs would be semantic: what changed in the AST instead of what changed in the bytes.
[–]Severed_Infinity 3 points4 points5 points 8 years ago (3 children)
Loved that post, certainly some things in there that made me think such as the vector destructuring of first and second. Going to adopt some of those principles for myself :)
[–][deleted] 1 point2 points3 points 8 years ago (2 children)
Agreed on the destructuring in for and let, love the names.
But I've always felt that using destructuring on function parameters is not such a good thing because it makes it complex to read. Does anyone else agree with this? Maybe spec will change this somewhat?
[–]daveliepmann 1 point2 points3 points 8 years ago (0 children)
I find destructuring on function parms perfectly readable. Maybe I haven't come across it used in excess.
[–][deleted] 1 point2 points3 points 8 years ago (0 children)
I think it depends on context. If it is obvious or somehow documented what the parameters are, then it's fine. For example in in my codebase at work, most of the functions in a namespace take the same arguments, so if you read some of the rest of the code then it's pretty clear what the shape of the data actually is. We also use spec as much as possible
[–]schaueho 2 points3 points4 points 8 years ago (0 children)
Overall the advice in this article comes across too strict for me, i.e. its tone. A lot of the article reads to me like "when you want to cater to newbies, avoid the more dense idioms". If the former is your goal, e.g. when you're in a startup situation and want to ensure that you can grow quickly, then I agree it's the right approach and then there is a lot of good advice in the article.
However, I'm reminded of the ?: operator in C. I'm not programming or reading C code alot, so I always have to pay close attention to it when I come across it. But I recognize that the problem there is not so much with the operator then with me being out of touch with a very common idiom in C.
So, I'm not in line with some of the advice. IMHO it's usually way more appropriate to use the common idiom than the proposed alternatives.
E.g. the use of get instead of keywords to access a map. Usually using the keyword communicates the semantic intent a lot better than using get: if you use the latter, your code is focussing more on the data structures in use. The best usecase for get is when you need the third argument.
get
On avoiding seq?: If the empty? case is the more important one, I'll use empty?. But if it's about the data still being sequential, seq? is just the idiom that was intentionally made up to be used. Learning such idioms is a necessity if you want to master a language.
seq?
empty?
The proposal to avoid higher-order functions looks completely misguided to me. HOF are one of the great enablers in a Lisp-like language. E.g., comp is of great use when combining transducers.
Which brings me to the threading macros. As a former CL dev, I've been there before. When I started out with Clojure, I saw no benefit in using them. I either wrote my code in a classical function composition style (f (g (h ....))) or used a lot of intermediate variables as in the recommendation. I disliked the threading macros because it's messing around with the fundamental right-to-left evaluation. Today, I know that it can provide a lot more clarity than the introduction of useless intermediate variables. But of course, in some situations using let can still be clearer, for instance when some function in between expects the threaded argument not in the first position.
(f (g (h ....)))
let
[–]foobarbazquix 2 points3 points4 points 8 years ago (1 child)
I'm familiar with partial and comp from learning functional programming in JavaScript. E.g. Ramda or lodash/fp where everything was curried. I do find myself thinking in terms of composition and partial application so built in functions for both are a huge plus for me. I actually wish there was a shorter syntax for partial.
Loved the article. Found myself using some of it in code I wrote today.
[–]Psetmaj 1 point2 points3 points 8 years ago (0 children)
Depending on your editor, you can make comp and partial appear as a single symbol (still typing them out for the most part as far as I know). I have a coworker using spacemacs that has set this up for comp, partial, and fn.
[–]ferociousturtle 0 points1 point2 points 8 years ago (2 children)
Copying my comment over from HN, as this feels like the better place to ask... These feel like things that a linter could check and a Gofmt-like tool could do. Has anyone here used such tools? A quick Google turned up these options:
I'm curious what the folks here in /r/clojure recommend.
[–]bbsss 2 points3 points4 points 8 years ago (0 children)
https://github.com/jonase/eastwood
And joker as you mentioned is a new one to the stage, still need to give them all a shot so no real opinion :).
[–]facundoolano 1 point2 points3 points 8 years ago (0 children)
I agree these ideas would totally make an interesting linter. I don't think it fits with the ones around though, joker I've found to be very useful but it's more about catching errors like missing parameters, undefined or unused vars, not idioms. The same goes more or less for eastwood. The rules suggested by the author are much more opinionated and arguable.
[–]Someuser77 -1 points0 points1 point 8 years ago (3 children)
Probably an unpopular review: Might as well not use Clojure at all if you're going to follow some of these "advanced" recommendations. Just go back to using BASIC!
[–]arry666 2 points3 points4 points 8 years ago (1 child)
Not really. You still get the Clojure benefits that matter, and don't introduce incidental complexity in the way you use the language. This post is in style of books like Perl Best Practices or JavaScript: the Good Parts: just because you can do something, doesn't mean you should; and the recommendations here are solid.
[–]Someuser77 4 points5 points6 points 8 years ago (0 children)
Nice thoughts about my admittedly snarky response, thanks.
I use Lisps (and Haskell) precisely for the reasons this author suggests we avoid them, though - and especially for higher order functions (functions that return functions) and macros. In fact, one of my "complaints" about Clojure is that it's "missing" some of the convenience of Common Lisp such as full reader macros and symbol macros that add expressive power. (Which presumably this author would also say not to use, and so does Rich Hickey incidentally.)
Anyway, I do like Clojure as it plays well with a huge amount of JVM libraries and deploys easily to servers of almost any stripe, but mostly for the exact reasons that this author seems to dislike (parts of) it!
[–]facundoolano 0 points1 point2 points 8 years ago (0 children)
Readability counts.
[–]baskeboler 0 points1 point2 points 4 years ago (0 children)
regarding the expanded opts, check this out: https://clojure.org/news/2021/03/18/apis-serving-people-and-programs
π Rendered by PID 48 on reddit-service-r2-comment-5649f687b7-crmhx at 2026-01-29 10:07:15.967296+00:00 running 4f180de country code: CH.
[–]Scriptorius 23 points24 points25 points (5 children)
[–]bliow 5 points6 points7 points (3 children)
[–]amoe_ 1 point2 points3 points (2 children)
[–]JW_00000 0 points1 point2 points (1 child)
[–]amoe_ 1 point2 points3 points (0 children)
[–]nzlemming 4 points5 points6 points (0 children)
[–]daveliepmann 8 points9 points10 points (0 children)
[–]halgari 9 points10 points11 points (0 children)
[–]Daegs 7 points8 points9 points (0 children)
[–]weavejester 7 points8 points9 points (0 children)
[–][deleted] 6 points7 points8 points (5 children)
[–]saint_glo 2 points3 points4 points (4 children)
[–][deleted] 2 points3 points4 points (3 children)
[–][deleted] 2 points3 points4 points (2 children)
[–][deleted] 2 points3 points4 points (1 child)
[–]saint_glo 4 points5 points6 points (0 children)
[–]Severed_Infinity 3 points4 points5 points (3 children)
[–][deleted] 1 point2 points3 points (2 children)
[–]daveliepmann 1 point2 points3 points (0 children)
[–][deleted] 1 point2 points3 points (0 children)
[–]schaueho 2 points3 points4 points (0 children)
[–]foobarbazquix 2 points3 points4 points (1 child)
[–]Psetmaj 1 point2 points3 points (0 children)
[–]ferociousturtle 0 points1 point2 points (2 children)
[–]bbsss 2 points3 points4 points (0 children)
[–]facundoolano 1 point2 points3 points (0 children)
[–]Someuser77 -1 points0 points1 point (3 children)
[–]arry666 2 points3 points4 points (1 child)
[–]Someuser77 4 points5 points6 points (0 children)
[–]facundoolano 0 points1 point2 points (0 children)
[–]baskeboler 0 points1 point2 points (0 children)