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.
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?
* 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.
Align let bindings in two columns - this is purely a matter of preference. I don't care either way.
I find that if they're not aligned and the identifiers are of varying length, that the names and code bleeds together making it hard to read which are the names and what is part of the code. Its especially bad if the code for a binding is more than one line long (which should be avoided, but isn't always possible without factoring it into a function)
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.
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.
Based on the examples presented, it seems like the OP doesn't actually mean "higher-order functions" in general. The "good" example still uses map, which is a higher-order function. It seems like what OP has a problem with is specifically functions that return functions.
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.
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"
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.
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.
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.
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.
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.
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.
> - 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)
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?
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.
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:
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).
[+] [-] dustingetz|8 years ago|reply
[+] [-] dghf|8 years ago|reply
There's a reason why (seq coll) is a standard Clojure idiom, which the source for empty? reveals:
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:
> correct, error-proof way to choose "first non-nil value" I'm sorry, but that's horrible. What if you had 20 items to check? Or an indeterminate number?How about:
[+] [-] escherize|8 years ago|reply
[+] [-] lambdadmitry|8 years ago|reply
Regarding the last example: I would say
is even better. However, if that vector is effectively a tuple (so a, b, and c are heterogenous and can be named), cond is more explicit and readable.[+] [-] dm3|8 years ago|reply
* 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.
[+] [-] dkersten|8 years ago|reply
I find that if they're not aligned and the identifiers are of varying length, that the names and code bleeds together making it hard to read which are the names and what is part of the code. Its especially bad if the code for a binding is more than one line long (which should be avoided, but isn't always possible without factoring it into a function)
[+] [-] kimi|8 years ago|reply
[+] [-] abc_lisper|8 years ago|reply
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.
[+] [-] emidln|8 years ago|reply
[+] [-] chc|8 years ago|reply
[+] [-] adambard|8 years ago|reply
[+] [-] lacampbell|8 years ago|reply
I don't know if that's true anymore. HoF are becoming mainstream - and rightly so.
Java, C++, Python, C#, VB.NET, Javascript, PHP, Perl, Ruby.. they all have them.
[+] [-] devin|8 years ago|reply
Threading enhances readability so long as it is not overused.
[+] [-] pgt|8 years ago|reply
[+] [-] dkersten|8 years ago|reply
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|8 years ago|reply
[+] [-] bryanlarsen|8 years ago|reply
http://readable.sourceforge.net/
[+] [-] yjgyhj|8 years ago|reply
[+] [-] weavejester|8 years ago|reply
[+] [-] yjgyhj|8 years ago|reply
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|8 years ago|reply
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|8 years ago|reply
[+] [-] z3t4|8 years ago|reply
[+] [-] dkersten|8 years ago|reply
[+] [-] yomly|8 years ago|reply
[+] [-] dghf|8 years ago|reply
> - 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:
> By contrast, to understand following code you don’t need any context: 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
with 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:
[+] [-] latte|8 years ago|reply
[+] [-] Plugawy|8 years ago|reply
[+] [-] emidln|8 years ago|reply
[+] [-] dantiberian|8 years ago|reply
> 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|8 years ago|reply
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.
[+] [-] throwanem|8 years ago|reply
[+] [-] christophilus|8 years ago|reply
- Lint: https://github.com/candid82/joker - Gofmt: https://github.com/pesterhazy/boot-fmt
[+] [-] megawatthours|8 years ago|reply
[+] [-] Touche|8 years ago|reply
[+] [-] valw|8 years ago|reply
[+] [-] unknown|8 years ago|reply
[deleted]
[+] [-] mhd|8 years ago|reply
[+] [-] perryprog|8 years ago|reply
[+] [-] joncampbelldev|8 years ago|reply
[+] [-] nerdponx|8 years ago|reply