From the type sense, there are distinctions between primitive values and references. From the semantics sense, most clojure types are references (except for the typical host platform’s primitive unboxed types), and that includes the persistent collections and literals you mentioned. These clojure types have an immutable “value” semantic that means they obey structural equality (via clojure.core/=
) and they never change (are immutable). So from a comparison perspective, despite being implemented by underlying reference types (or boxed types), they have immutable value semantics from a functional programming perspective.
A third layer is looking at what can be treated as “data”, or passed around to arguments of functions. This includes the aforementioned primitive types, the persistent collections and immutable reference types Clojure provides, the mutable reference types (atoms, vars, agents, volatiles), mutable arrays, and any host object that can potentially mutate. These are all valid “data” that could show up as an argument to a function. Just about the only things that aren’t passed around as values (or able to be evaluated) are macros and the interop method invocations (.toString is not a function that can be passed around as data).
We typically leverage the rich naming capabilities in clojure to help with this stuff, since there aren’t a lot of reserved words or limitations on the syntax for naming stuff.
A lot of folks inherit xs
from the FP/haskell community to denote a sequence or collection of values, and x
to denote a single value.
(defn sum [xs] (reduce + xs))
;or
(defn sum [xs] (reduce (fn [acc x] (+ x acc)) 0 xs))
(defn odds [xs]
(for [x xs
:when (odd? x)]
x)
Maybe name argument with map if it works on a hashmap…
(defn update-score [score-map player new-score]
(assoc score-map player new-score))
If you read the clojure.core source and other libraries, you will see coll
a lot
to refer to a clojure collection.
(defn odds [coll] (filter (fn [x] (odd? x)) coll))
k
and v
show up a lot in destructuring, or for arguments when dealing with functions like assoc
that work with a key and a value in an associative context like a hashmap.
There are some idioms around conveying mutation and reference types in the naming of arguments…
Derived from the convention for dynamic variables (an enclosing pair of * “earmuffs”),
some people use a single earmuff to denote a reference type, and denote side-effecting operations with a ! in the name. I tend to not name atoms specially. I do sometimes refer to them in the argument name though.
(defn add-to! [total-atom x]
(swap! total-atom + x))
(defn add-to! [atm x]
(swap! atm + x))
You can be as verbose as you would like to when naming stuff. Some folks following scheme conventions are likely to be very “literate”:
(defn add-to-atom-and-return-the-atom [the-atom x]
(swap! the-atom + x)
the-atom)
I find it useful to adopt the scheme convention of transformations delineated in the name too:
(defn names->initial-scores-map [names]
(into {} (map (fn [k] [k 0])) names))
(defn scores-map->players [m]
(keys m))
(defn scores-map->scores [m]
(vals m))
There’s a lot of flexibility, and many existing idioms in the wild.
You might be interested in one of the popular clojure style guides too.