I am new to Clojure. I can understand the code I write but it becomes too difficult to understand it later.It becomes difficult to match parentheses.
What are the generic conventions to follow regarding naming conventions andindentation in various situations?
For example I wrote a sample de-structuring example to understand but it looks completely unreadable the second time.
(defn f [{x :x y :y z :z [a b c] :coll}] (print x " " y " " z " " a " " b " " c))
In case of de-structuring, is it better to do it directly at the parameter level or start a let form and then continue there?
-
3There is a good answer about readability at Stack Overflow. You can check it out. stackoverflow.com/a/1894891/1969106yfklon– yfklon2013年05月07日 07:19:19 +00:00Commented May 7, 2013 at 7:19
-
2Writing readable Lisp code is difficult in general. They invented the backronym "Lost In Superfluous Parenthesis" for a reason.Mason Wheeler– Mason Wheeler2013年05月21日 18:26:59 +00:00Commented May 21, 2013 at 18:26
1 Answer 1
Naming conventions
- stay lowercase for functions
use
-
for hyphenation (what would be underscore or camel case in other languages).(defn add-one [i] (inc i))
Predicates (i.e. functions returning true or false) end with
?
Examples:odd?
even?
nil?
empty?
State changing procedures end in
!
. You rememberset!
right? orswap!
Choose short variable name lengths depending on their reach. That means if you have a really small auxiliary variable you can often just use a one-letter name.
(map (fn [[k v]] (inc v)) {:test 4 :blub 5})
choose longer variable names as needed, especially if they are used for lots of code lines and you cannot immediately guess their purpose. (my opinion).I feel that a lot of clojure programmers tend to rather use generic and short names. But this is of course not really an objective observation. The point is that a lot of clojure functions are actually quite generic.
- Use meaningful names.Procedures are doing something, therefor you can best describe them by using verbs. Clojure built-in functions should put you on the right track:
drop
,take
,assoc
, etc. Then there is a nice article describing ways to choose a meaningful name: http://ecmendenhall.github.io/blog/blog/2013/09/02/clean-clojure-meaningful-names/
- Use meaningful names.Procedures are doing something, therefor you can best describe them by using verbs. Clojure built-in functions should put you on the right track:
Lambda functions
You can actually name lambda functions. This is convenient for debugging and profiling (my experience here is with ClojureScript).
(fn square-em [[k v]] {k (* v v)})
Use inline lambda functions
#()
as convenient
Whitespace
There should not be parens-only lines. I.e. close the parentheses right away. Remember parens are there for editor and compiler, indentation is for you.
Function parameter lists go on a new line
(defn cons [a b] (list a b))
This makes sense if you think about the doc strings. They are between the function name and parameters. The following doc string is probably not the wisest ;)
(defn cons "Pairing up things" [a b] (list a b))
- Paired data can be separated by a new line as long as you retain the pairing
(defn f [{x :x y :y z :z [a b c] :coll}] (print x " " y " " z " " a " " b " " c))
(You can also enter ,
as you like but this feels unclojurish).
For indentation use a sufficiently good editor. Years ago this was emacs for lisp editing, vim is also great today. Typical clojure IDEs should also provide this functionality. Just do not use a random text editor.
In vim in command mode you can use the
=
command to properly indent.If command get too long (nested, etc.) you can insert a newline after the first argument. Now the following code is pretty senseless but it illustrates how you can group and indent expressions:
(+ (if-let [age (:personal-age coll)] (if (> age 18) age 0)) (count (range (- 3 b) (reduce + (range b 10)))))
Good indentation means that you do not have to count brackets. The brackets are for the computer (to interpret the source code and to indent it). Indentation is for your easy understanding.
Higher order functions vs. for
and doseq
forms
Coming from a Scheme background I was rather proud to have understood map
and lambda functions, etc. So quite often, I would write something like this
(map (fn [[k x]] (+ x (k data))) {:a 10 :b 20 :c 30})
This is quite hard to read. The for
form is way nicer:
(for [[k x] {:a 10 :b 20 :c30}]
(+ x (k data)))
`map has a lot of uses and is really nice if you are using named functions. I.e.
(map inc [12 30 10]
(map count [[10 20 23] [1 2 3 4 5] (range 5)])
Use Threading macros
Use the threading macros ->
and ->>
as well as doto
when applicable.
The point is that threading macros make the source code appear more linear than function composition. The following piece of code is pretty unreadable without the threading macro:
(f (g (h 3) 10) [10 3 2 3])
compare with
(->
(h 3)
(g 10)
(f [10 3 2 3]))
By using the threading macro, one can typically avoid introducing temporary variables that are only used once.
Other Things
- Use docstrings
- keep functions short
- read other clojure code
-
That function with de-structuring looks beautiful with indentation!Amogh Talpallikar– Amogh Talpallikar2013年05月08日 05:28:42 +00:00Commented May 8, 2013 at 5:28
-
1+1 for keep functions short. Lots of little functions are much more self documentingdaniel gratzer– daniel gratzer2013年07月04日 05:39:48 +00:00Commented Jul 4, 2013 at 5:39
-
2I strongly disagree that it's a good idea to use short variable names, even in "short-reaching" functions. Good variable names are critical for readability and they cost nothing except key strokes. This is one of things that bothers me most about the Clojure community. There are many folks with an almost hostile resistance to descriptive variable names. Clojure core is littered with 1-letter variable names for function arguments, and that makes it much harder to learn the language (e.g. running
doc
orsource
in a REPL). End of rant, for an otherwise excellent answerNathan Wallace– Nathan Wallace2016年03月06日 23:38:35 +00:00Commented Mar 6, 2016 at 23:38 -
@NathanWallace In a way I agree with you, but in some aspects I dont. Long names sometimes tend to make functions overspecific. So you might find that some general filter operation is in fact general, while when the argument was
apples
instead ofxs
, you thought it was specific to apples. Then, also I would consider function argument names to be further reaching than let-say a for loop variable. so if need be, you can have them longer. As a last thought: I'll leave you with "Name code not values"concatenative.org/wiki/view/Concatenative%20language/…wirrbel– wirrbel2016年03月10日 07:50:46 +00:00Commented Mar 10, 2016 at 7:50 -
I might add a paragraph on something like private vs public interfaces. Especially regarding libraries. It is an aspect of code quality that is not talked about enough and I have learned tons about this since writing that answer.wirrbel– wirrbel2016年03月10日 07:54:51 +00:00Commented Mar 10, 2016 at 7:54