
Readable Clojure - tosh
http://tonsky.me/blog/readable-clojure/
======
dustingetz
Eh I dunno. Clojure comes closer than any other programming language to being
like spoken language in that there are tons of different tenses and
conjugations and a large, expressive vocabulary to articulate precisely what
you mean. Like when writing in English - decide who your audience is and then
use the vernacular they know to the full extent. In any serious project, that
pretty much means you get to assume at least that they are fluent in clojure,
before adding in other DSLs like funcool/cats (category theory) if your
problem domain needs it. If your audience needs guard rails, Clojure is
probably not the language for you for other reasons, for example the culture
of "just read the source" (which is a great thing i think!). Just my 2c as
someone who has written clojure full time for a couple years. Tonsky has
probably written more Clojure than I have.

------
dghf
> prefer (not (empty? coll)) over (seq coll),

There's a reason why _(seq coll)_ is a standard Clojure idiom, which the
source for _empty?_ reveals:

    
    
        (defn empty?
          "Returns true if coll has no items - same as (not (seq coll)).
          Please use the idiom (seq x) rather than (not (empty? x))"
          {:added "1.0"
           :static true}
          [coll] (not (seq coll)))
    

By using _(not (empty? coll))_ , you are effectively writing _(not (not (seq
coll)))_ , which isn't really very elegant, even if of marginal significance
performance-wise.

Curiously, he later champions the use of _(some? x)_ rather than the arguably
more readable _(not (nil? x))_ , even though in this case the former is
directly equivalent to the latter:

    
    
        (defn some?
          "Returns true if x is not nil, false otherwise."
          {:tag Boolean
           :added "1.6"
           :static true}
          [x] (not (nil? x)))
    

> correct, error-proof way to choose "first non-nil value"
    
    
        (cond
          (some? a) a
          (some? b) b
          (some? c) c)
    

I'm sorry, but that's _horrible._ What if you had 20 items to check? Or an
indeterminate number?

How about:

    
    
      (first (remove nil? [a b c]))

~~~
escherize
Must say I prefer or for this, and consider differentiating false and nil to
usually be a code smell:

    
    
        (or a b c)

~~~
dghf
The relevant bit of the article was explicitly concerned with situations where
false values needed to be returned if present, and ruled out the use of _or_
for precisely that reason.

But I agree: if you were ever in a position where you needed to do this, you
might want to reconsider how you were representing your data.

------
dm3
* Don’t use “use” - agree completely.

* Use consistent, unique namespace aliases - agree. This helps tremendously when consistent across projects given the varying tooling capabilities. We even have a dictionary of namespace -> alias mappings that is expanded as new commonly used namespaces appear.

* Use long namespace aliases - agree partially. I have a few favourite namespaces present in pretty much every project that get a single letter alias.

* Choose readability over compactness - agree partially. Another part of the solution is keeping the functions small and all the types explicit. However, there's a fine line between that and having to use something like a hungarian notation for the local variables.

* Don’t rely on implicit nil-to-false coercion - agree. However, I never find myself in this situation. Mostly because I just don't use plain booleans. Pretty much always you can use an enum (keyword) instead to better express the intent. When used locally - in the scope of a single function - I find that boolean-nil problem doesn't cause any issues.

* Avoid higher-order functions - agree completely. `comp` and `partial` in Clojure are awkward. If you find yourself using them, you're probably nesting too many lambdas with hash (#) notation - move some of them out into a `let`.

* Don’t spare names - agree partially. The suggestion is definitely more readable. I just love writing threading expressions.

* Don’t use first/second/nth to unpack tuples - agree completely.

* Don’t fall for expanded opts - agree completely.

* Use * as prefix for references - agree. This needs some sort of a blessed reference in the Clojure documentation. Something to syntactically mark constants, e.g. `+constant+`, something to mark refs, e.g. `+ref`.

* Align let bindings in two columns - this is purely a matter of preference. I don't care either way.

* Use two empty lines between top-level forms - also a matter of preference. I prefer a single line.

~~~
kimi
What is wrong with Hungarian notation? I use mX and vY for maps and vectors
when they are not totally transparent and it does help. Of course you have no
guarantee that your

    
    
       (defn foo [mA] 
         (:foo mA)) 
    

will be passed a map in mA but at least you are drawing a boundary.

~~~
dm3
I'm not a big fan - once you start encoding type information in names, you
have to be extremely attentive when refactoring. It doesn't scale too well in
a larger team.

I prefer _clojure.spec_ to guard significant (ns, api) boundaries.

------
abc_lisper
Agree with most of them, except,

Advise against higher order functions. Higher order functions separate clojure
from other languages, and is one of defining features of this class of
languages.

Advise to not use threading. Threading is just a series of steps. I find it
very readable. If I start using symbols the step sequence can become a step
graph FWIW.

~~~
adambard
I for one enthusiastically agree with the author's advice to use the `#()`
syntax in place of `partial` and/or `comp`. It's just plain easier to parse,
even for those who are familiar with higher-order functions but especially for
those that aren't.

~~~
nickfargo
That's interesting, because I find #() rather _less_ easy to parse compared to
(comp) or (partial) for otherwise equivalent expressions.

At a glance I know that (comp f g h) and its flat list of arguments expresses
a simple function composition chain. Seeing a #(f (g (h %))) where (comp) will
do makes me sift through more parens; more than that though, when (comp) is
embraced as convention, a #() is an immediate cue that simple function
composition is _not_ what's being expressed, which draws me in necessarily to
examine the particular structure of the #() body.

------
pgt
Like the *-prefix, I derived significant readability by prefixing all
reference type symbols (like atoms) with !, which goes hand-in-hand with the
exclamation mark in `swap!` and `reset!`. For example:

    
    
        (let [!input (atom {:some "data"}]
          (emit! ::event @!input) ;; @ and ! always go together
          (reset! !input {:new "data"})) ;; mutations against plain symbols "looks wrong"

~~~
dkersten
I like that. I think it reads a bit better because ! looks less like @. The
analogue with reset! and swap! is nice too.

However, right now I'm also working with Java (unfortunately :D) and !input
looks a bit too similar to a not condition, so adds a tiny bit to my language
context switch.

------
penpapersw
I've been bitten before trying to use (if (some? (thing))) instead of just (if
(thing)) for the sake of readability, because as (defn thing ...) evolved, it
ended up returning a function call that sometimes returned false instead of
just nil, so the conditions were sometimes inverting in subtle ways and it was
_really hard_ to track down why.

------
bryanlarsen
I was hoping that this would be an implementation of readable lisp (aka sweet
expressions) for clojure.

[http://readable.sourceforge.net/](http://readable.sourceforge.net/)

~~~
yjgyhj
Disagree that it's readable. The parens are great - they show exactly where a
function call starts and where it ends. The program is a tree!

~~~
camdez
+1 That thing is gross. I'd edit so much more slowly if I had to go back to a
messy, complex syntax. At least without a very clever / specialized editor
effectively undoing the change for me.

Infix is occasionally more readable for math, but I'd rather have a macro to
transform a delimited section of infix code than to mess with the language.

~~~
yjgyhj
Infix is only easy to read if you're only used to it. Not inherently better -
I'd say opposite even.

Also, I don't want to live without Paredit (or Smartparens)

------
yjgyhj
One thing me and my teammate did in our last Clojure gig was to not shorten
namespaces (with the `:as` keyword).

Instead of in this example write `[cognician.chat.dom :as dom]` just write
`cognician.chat.dom`. Then when calling, use the whole namespace.

It's a tradeoff between typing a little bit more (in reality, using the
autocomplete function) in exchange for having the code readable. When you read
something on line 213, you don't have to scroll up to double check what that
short name refers to.

Wish :as was never added to begin with.

------
dj-wonk
I remember having these kinds of discussions with coworkers in a previous job
where I did Clojure daily.

I don't necessarily want to comment on the particular details here. You may
agree or disagree with elements of any one particular style guide; many depend
on your team and audience.

With that said, I would make these comments. First, your team might find value
in choosing an existing style guide and updating it as you go. Second, it
might use pairing as a way of letting these decisions happen organically.

~~~
draw_down
A lot of these can be linter rules, like no single-letter aliases, or forcing
common packages to be imported as one consistent name. (I don't know a ton
about clojure but I'm assuming linting is a thing.) To the extent that linting
keeps things consistent and helps prevent mistakes, without unnecessarily
hamstringing developers, it's a good idea imo.

------
z3t4
Advice for the author, skip most of the "don't" if you write both the bad and
the good, it will be confusing and the reader will remember both, but after
some time forget which was better.

~~~
dkersten
I found the comparisons useful. It showed me why its better, instead of just
saying "hey this is better, do this".

------
dghf
> My (incomplete) set of personal rules:

> \- use contains? instead of using sets as functions,

[snip]

> An example. To understand this piece of code you need to know that possible-
> states is a set:
    
    
          (when (possible-states state)
            ... )
    

> By contrast, to understand following code you don’t need any context:
    
    
          (when (contains? possible-states state)
            ... )
    

I disagree. In the first example, it's clear from context that _possible-
states_ is a function (to be precise, an object that implements _IFn_ ) that
returns a truthy or falsy value depending on the value of _state_ ; the name
_possible-states_ suggests it's checking that the value of _state_ is valid,
according to some criteria.

To determine what those criteria are, you'd have to look at the definition of
_possible-states_ : but that would also be true even if you used the more
verbose _contains?_ construct.

By not using _contains?_ , you also retain the option to replace _possible-
states_ with an actual function, should you later discover you need further
validation or processing not possible with a simple set.

For example, if _state_ is a text string, and you find out further down the
line that sometimes it's in the wrong case or has unwanted leading or trailing
white space, you can replace

    
    
        (def possible-states #{"foo" "bar" "baz"})
    

with

    
    
        (defn possible-states [state]
          (->> state
            clojure.string/trim
            clojure.string/lower-case
            #{"foo" "bar" "baz"}))
    

without needing to change code elsewhere.

Even if you don't feel that this flexibility is worth the ambiguity,
_contains?_ offers little comfort, as it can take many things other than a
set:

    
    
        (contains? #{"foo"} "foo")     ; true
        (contains? {"foo" 1} "foo")    ; true  ("foo" is a key of the map)
        (contains? {:bar "foo"} "foo") ; false ("foo" is a value but not a key)
        (contains? ["foo" "bar"] 1)    ; true  (vectors are keyed by integers)
        (contains? '("foo" "bar") 1)   ; false (but lists aren't)
        (contains? "foo" 1)            ; true  (but strings are)

------
latte
I find the advice on prefixing atom names with * very useful, especially for
Clojurescript / Reagent code. I have never encountered such style before
though - is it a widely accepted convention?

~~~
emidln
I haven't seen it. I don't find myself using atoms very much and when I am
using atoms, it's almost always because I'm trying to use doseq when a more
functional version of my code exists if I would think about it.

~~~
latte
From my experience, atoms are indeed rarely needed on the backend, where you
have a DB to store the app state, but you can't do pretty much anything
without them in cljs / Reagent.

------
dantiberian
> Align let bindings in two columns

> I do it by hand, which I consider to be a small price for readability boost
> that big. I hope your autoformatter can live with that.

Cursive's formatter has this option.

~~~
nathell
Is it just me who doesn't find this to boost readability?

It also has downsides (overly large diffs if you need to adjust alignment
after adding a new binding-with-a-long-name to a let clause).

I also find two empty lines between functions to be too much for my taste.

~~~
Stratoscope
I'm with you on the column alignment. I don't see how the items in the right
column have so much to do with each other that reading down the column is
helpful. All that really gets aligned are the opening parentheses, and what's
the point of that?

Lining up columns like this can make you crazy. There's a good example of this
earlier on the page:

    
    
      (ns examples.long-aliases
        (:require
          [clojure.spec          :as spec]
          [clojure.test          :as test]
          [clojure.java.io       :as io]
          [rum.core              :as rum]
          [diatomic.api          :as diatomic]
          [clojure.string        :as string]
          [cognician.chat.util   :as util]
          [cognician.chat.server :as server]
          ;; you can use dots in aliases too
          [cognician.chat.server.schema   :as server.schema]
          [cognician.chat.ui.entries.core :as ui.entries.core]))
    

Here we have two aligned sections, one above and one below the ;; comment. Why
aren't they _all_ aligned to the same column? Apparently it's the ;; comment
that says "OK, you can stop worrying about alignment here and start a new
alignment section below this line."

Now what if we remove the comment?

    
    
      (ns examples.long-aliases
        (:require
          [clojure.spec          :as spec]
          [clojure.test          :as test]
          [clojure.java.io       :as io]
          [rum.core              :as rum]
          [diatomic.api          :as diatomic]
          [clojure.string        :as string]
          [cognician.chat.util   :as util]
          [cognician.chat.server :as server]
          [cognician.chat.server.schema   :as server.schema]
          [cognician.chat.ui.entries.core :as ui.entries.core]))
    

Well, that won't do. Time to realign everything to keep it clean:

    
    
      (ns examples.long-aliases
        (:require
          [clojure.spec                   :as spec]
          [clojure.test                   :as test]
          [clojure.java.io                :as io]
          [rum.core                       :as rum]
          [diatomic.api                   :as diatomic]
          [clojure.string                 :as string]
          [cognician.chat.util            :as util]
          [cognician.chat.server          :as server]
          [cognician.chat.server.schema   :as server.schema]
          [cognician.chat.ui.entries.core :as ui.entries.core]))
    

Now we see another problem with alignment. The sheer amount of horizontal
white space makes it hard to visually match up the first several lines that
have much names on the left.

And how do we decide what to align and what _not_ to align? We're lining up
the :as, but why not the closing brackets too?

    
    
      (ns examples.long-aliases
        (:require
          [clojure.spec                   :as spec           ]
          [clojure.test                   :as test           ]
          [clojure.java.io                :as io             ]
          [rum.core                       :as rum            ]
          [diatomic.api                   :as diatomic       ]
          [clojure.string                 :as string         ]
          [cognician.chat.util            :as util           ]
          [cognician.chat.server          :as server         ]
          [cognician.chat.server.schema   :as server.schema  ]
          [cognician.chat.ui.entries.core :as ui.entries.core]))
    

Plus, alignment obviously works only in a monspaced font. I like to make my
code readable whether someone uses a monospaced or proportional font. If you
forgo alignment, code is equally readable in any kind of font.

And really is non-aligned code any less readable than the aligned version? It
may not be quite as pretty, by some definition of "pretty", but does it really
matter? And it has the advantages of not requiring constant fiddling as you
add names, not messing up your VCS diffs, and keeping the left and right parts
of the :as close together so you can easily match them up visually?

    
    
      (ns examples.long-aliases
        (:require
          [clojure.spec :as spec]
          [clojure.test :as test]
          [clojure.java.io :as io]
          [rum.core :as rum]
          [diatomic.api :as diatomic]
          [clojure.string :as string]
          [cognician.chat.util :as util]
          [cognician.chat.server :as server]
          [cognician.chat.server.schema :as server.schema]
          [cognician.chat.ui.entries.core :as ui.entries.core]))

~~~
taeric
Sadly, for me you made the opposite point. The column aligned sections were
_far_ easier to see the sections than the other.

Now, I do think this is of rather limited value. So I wouldn't die on this
hill. But, column alignment is clearly superior to my subjective eye. It is a
shame not everyone has align-regexp.

~~~
spinningarrow
Could it be a matter of syntax highlighting? I find highlighting draws the
subjects out to me better. In my case the :as would be a different color and
the closing bracket a muted color, which makes it easy for me to pick out the
importing names.

~~~
dkersten
Not for me. My code is syntax highlighted, but I still personally find the
aligned code easier to see. The difference is between having all of the stuff
side-by-side (vertically) and looking for colours inside other code fragments.

Definitely subjective, though, I guess.

------
christophilus
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:

\- Lint:
[https://github.com/candid82/joker](https://github.com/candid82/joker) \-
Gofmt: [https://github.com/pesterhazy/boot-
fmt](https://github.com/pesterhazy/boot-fmt)

------
megawatthours
Very well designed website.

------
Touche
For such a young language Clojure seems to have a lot of cruft. 3 ways to
import a module, what were they thinking?

~~~
valw
Clojure is almost 10 years old. I don't think this reasoning makes sense
though, because from what I've seen most of the cruft in any language comes
from the very early design decisions (both in what's included and what's
missing).

~~~
Touche
Cruft in a language isn't such a big deal as long as it is fixed. What really
matters is cruft in the ecosystem. There are so many Clojure tutorials that
use "use" and "refer". That's the real problem. JavaScript has with(), but
there are approximately 0 articles online that use it, so its not an issue.

------
mhd
Is that an actual editor in the screenshots? I thought most clojurists use
emacs...

~~~
perryprog
I used to, but now I use Cursive in IntelliJ IDEA.

------
joncampbelldev
quite sensible advice, will probably start minimising my use of refer. I don't
agree with all of it, but that's the nature of style guides.

------
nerdponx
A lot of this applies to Python as well.

