Search code examples
clojureprotocolsreference-manual

which protocol defines conj in clojure?


lets say I wrote a function:

(defn foo [to x] (conj to x))

and would like to document it by stating that to must implement some protocol (as in the structure/type to must support the call conj). Is there a website or database that has this information? Obviously I would like to generalise this question to "where can I find a complete reference for all clojure protocols?"

As a clear and concrete example using Sam Estep's suggestion it would look like:

(defn invert-many-to-one
  "returns a one-to-many mapping where vals are collections of type `(constructor-fn)`,
   (defaults to `hash-set`). Note that `constructor-fn` is a function of 0 args.
  `insert-fn` function can be passed. if only `constructor-fn` is passed
  then `insert-fn` defaults to `conj` and `(constructor-fn)` must be an instance
  of `clojure.lang.IPersistentCollection`"
  ([m] (invert-many-to-one hash-set conj m))
  ([constructor-fn m] {:pre [(instance? clojure.lang.IPersistentCollection (constructor-fn))]}
   (invert-many-to-one constructor-fn conj m))
  ([constructor-fn insert-fn m]
   (persistent!
    (reduce (fn [m [k v]]
              (assoc! m v (insert-fn (clojure.core/get m v (constructor-fn)) k)))
            (transient {}) m))))

Solution

  • Unfortunately protocols weren't introduced until Clojure 1.2, and by then, all the built-in data structure abstractions had already been implemented as Java interfaces instead of protocols. This has the disadvantages you would expect, but while reimplementing all those abstractions as protocols was appropriate for ClojureScript since it was created after protocols were introduced, it would be infeasible to retrofit them into the JVM Clojure.

    If you look at the source code for conj, you'll see that it calls clojure.lang.RT/conj, which requires that its first argument implements the IPersistentCollection interface. Thus, you could write your function like this:

    (defn foo [to x]
      {:pre [(instance? clojure.lang.IPersistentCollection to)]}
      (conj to x))
    

    For your generalization, I would point you to a question that I asked in the past about implementing Clojure's core interfaces. If the answers there are not sufficient for your question, please let me know and I will add more details here.

    I would make a few minor adjustments to your invert-many-to-one function:

    (defn invert-many-to-one
      "Returns a one-to-many mapping where vals are collections of type
      `(constructor-fn)` (defaults to `hash-set`). Note that `constructor-fn` is a
      function of 0 args. `insert-fn` function can be passed. If only
      `constructor-fn` is passed then `insert-fn` defaults to `conj`.
      `(constructor-fn)` must be an instance of
      `clojure.lang.IPersistentCollection`."
      ([m]
       (invert-many-to-one hash-set m))
      ([constructor-fn m]
       (invert-many-to-one constructor-fn conj m))
      ([constructor-fn insert-fn m]
       {:pre [(instance? clojure.lang.IPersistentCollection (constructor-fn))]}
       (persistent!
        (reduce (fn [m [k v]]
                  (assoc! m v (insert-fn (get m v (constructor-fn)) k)))
                (transient {}) m))))
    
    • I changed the arity-1 body to call the arity-2 body instead of the arity-3 body; this way, you only specify conj as the default in one place.
    • I moved the precondition into the arity-3 body so that it will be used in all cases.
    • I replaced clojure.core/get with just get for idiomaticity.

    Of course, these three changes have their own drawbacks, as you pointed out in your comments, so definitely take them with a grain of salt.