Search code examples
clojurecoding-styleconventions

is clojure open to whatever works regarding naming conventions (of fns), especially regarding generic-like fns as partials


I'm quite new to functional programming and clojure. I like it. I'd like to know what the community thinks about following fn-naming approach: Is it a feasible way to go with such naming, or is it for some reason something to avoid?

Example:

(defn coll<spec>?
  "Checks wether the given thing is a collection of elements of ::spec-type"
  [spec-type thing]
  (and (coll? thing)
       (every? #(spec/valid? spec-type %)
               thing)))

(defn coll<spec>?nil
  "Checks if the given thing is a collection of elements of ::spec-type or nil"
  [spec-type thing]
  (or (nil? thing)
      (coll<spec>? spec-type thing)))

now ... somewhere else I'm using partial applications in clojure defrecrod/specs ... similar to this:

; somewhere inside a defrecord
^{:spec (partial p?/coll<spec>?nil ::widgets)} widgets component-widgets

now, I created this for colls, sets, hash-maps with a generic-like form for specs (internal application of spec/valid? ...) and with a direct appication of predicates and respectively a <p> instead of the <spec>

currently I'm discussing with my colleagues wether this is a decent and meaningful and useful approach - since it is at least valid to name function like this in clojure - or wether the community thinks this is rather a no-go.

I'd very much like your educated opinions on that.


Solution

  • It's a weird naming convention, but a lot of projects use weird naming conventions because their authors believe they help. I imagine if I read your codebase, variable naming conventions would probably not be the thing that surprises me most. This is not an insult: every project has some stuff that surprises me.

    But I don't see why you need these specific functions at all. As Sean Corfield says in a comment, there are good spec combinator functions to build fancier specs out of simpler ones. Instead of writing coll<spec>?, you could just combine s/valid? with s/coll-of:

    (s/valid? (s/coll-of spec-type) thing)
    

    Likewise you can use s/or and nil? to come up with coll<spec>?nil.

    (s/valid? (s/or nil? (s/coll-of spec-type) thing))
    

    This is obviously more to write at each call site than a mere coll<spec>?nil, so you may dismiss my advice. But the point is you can extract functions for defining these specs, and use those specs.

    (defn nil-or-coll-of [spec]
      (s/or nil? (s/coll-of spec)))
    
    (s/valid? (nil-or-coll-of spec-type) thing)
    

    Importantly, this means you could pass the result of nil-or-coll-of to some other function that expects a spec, perhaps to build a larger spec out of it, or to try to conform it. You can't do that if all your functions have s/valid? baked into them.

    Think in terms of composing general functions, not defining a new function from scratch for each thing you want to do.