Is everything a value?

Dear Clojure forum members,

I’m new to Clojure and functional programing and I’d like to kindly ask you one basic question that I’m unable to find an answer to (I searched a lot…).

Is every single thing in Clojure a value?

Based on my understanding:

  • Function is a value
  • List is a value and every element in a list is a value
  • Map is a value
  • Key in a map might be for example a string or a keyword and therefore the key itself is a value

From terminology perspective:

If I have a polymorphic function with a single argument, that does something generic (print it), I can name that argument “value” or “val”.
For example in different languages I would call it “input” since it might take both objects and primitives / value types and reference types and therefore “value” would be wrong name for such argument.

Thank you.

Dan

1 Like

Atoms and refs hold states, they are like references, but wrapped in dynamic types. Also Clojure may hold objects from host platform, some of them are references such as Java Objects.

I’d probably run with x, or thing, out some such.

1 Like

Thank you.

input for generic argument name sounds better in my opinion ;-). Thank you.

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.

8 Likes

¯\_(ツ)_/¯

Like @joinr mentions, a lot of the conventions are inspired from Clojure core itself. Your usage there with doing something generic with about anything is somewhat similar to pr, I think, which is defined like so:

(defn pr
  "Prints the object(s) to the output stream that is the current value
  of *out*.  Prints the object(s), separated by spaces if there is
  more than one.  By default, pr and prn print in a way that objects
  can be read by the reader"
  {:dynamic true
   :added "1.0"}
  ([] nil)
  ([x]
     (pr-on x *out*))
  ([x & more]
   (pr x)
   (. *out* (append \space))
   (if-let [nmore (next more)]
     (recur (first more) nmore)
     (apply pr more))))
3 Likes

“val” would not be a good choice, because it’s the name of a standard function. You won’t be able to use the function val inside the function that uses “val” as a parameter name. It’s not a function that gets used that much, I think, but still …

(You have probably already realized that since functions are just values of variables, Clojure will let you set the value of that variable, or use the name for a local variable, and then the function value will be unavailable–unless you take special steps. Recent versions of Clojure issue a warning message in some cases if you redefine a built-in function name, but you can still do it.)

Also, I personally wouldn’t use “value” as a generic parameter name, because maps are very common data structures in Clojure, and maps are described as associating keys and values. So if someone was reading your code and saw “value”, it would be natural for them to assume that the argument of the function is supposed to be a value that came from a map. Of course, there could be some contexts where “value” communicates something else that is useful, but that doesn’t seem to be what you are trying to do.

Carrying similar thinking over to the point made by @joinr and @PEZ, there’s nothing wrong with using “input” if that’s what’s clearest to you or to people you know who are working with your code, but if people who use Clojure a lot will eventually read your code, using common Clojure naming conventions will help them. (There are a couple of unusual naming conventions that I use in some of my code, and I usually include extra comments or other documentation to warn anyone who might read my code about these weird conventions.)

3 Likes

You can if you use the qualified form, clojure.core/val, but that’s working around the valid problem you brought up (name collision). I still find myself doing this kind of stuff every once in a while when naming things. Lexically shadowing library functions is interesting because the compiler doesn’t warn you, where if you go to defn something you get a nice warning about redefining the binding.

1 Like

Yeah–that’s one of the “special steps” I alluded to in another part of the post. You could also assign the value of val to another name and use that name to call the function. It’s a good way to obfuscate your code, though.

Yes, I often find myself checking at the repl to make sure a name isn’t in use. It makes me appreciate Common Lisp’s lisp-2 strategy, although the resulting extra syntax requirements in CL makes it less fun to use functions as values. A lot of what I love about Clojure is the fun of it, so I’m happy to put up with checking for name clashes. It helps if the editor configuration colors standard function names. (I’m one of the unusual Clojure fans who uses vim, and it does an OK job with giving standard function names special coloring.)

I don’t know which version of Clojure began issuing that warning. I was away from Clojure for a period, and then got the latest version and started seeing it. I suppose it might be easier to perform the check on anything that expands into def than on lexical variables. Maybe we’ll get a lexical variable warning in the future.

1 Like

Thank you very much @joinr @mars0i and @PEZ for super verbose description. Especially thx for explanation of the naming. It’s definitely a good idea to follow convention so x and xs sounds good.

I’ll definitely read the style guide and I’ll try to read clojure.core. It’s funny that I haven’t actually realized that I can read source of the language. Thank you.

This is a very good point. For maps it’s a good naming since a “key” is always associated with a “value” and both key and value can be (almost) anything.

2 Likes

The function clojure.repl/source is useful for looking up stuff at the repl.

user>(use 'clojure.repl)
nil
user>(source dissoc)
(defn dissoc
  "dissoc[iate]. Returns a new map of the same (hashed/sorted) type,
  that does not contain a mapping for key(s)."
  {:added "1.0"
   :static true}
  ([map] map)
  ([map key]
   (. clojure.lang.RT (dissoc map key)))
  ([map key & ks]
   (let [ret (dissoc map key)]
     (if ks
       (recur ret (first ks) (next ks))
       ret))))

Some of the core functions were pretty simple, but you might see some stuff pop up (primarily with the sequence libraries, many of which also support a transducer variant) that you don’t understand off the bat - or quite a bit of interop or other lower level optimizations specific to core or library code. Still, it’s a useful source of learning and you can get the source for most things at runtime.

3 Likes

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.