which protocol defines conj in clojure?
Asked Answered
A

1

6

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))))
Alltime answered 21/7, 2017 at 22:44 Comment(4)
Honestly, I wish I could upvote your question twice; I'm quite impressed.Fons
Your question is more general, and the answer you've gotten is better than the one I gave five years ago, so I won't close it as a duplicate, but for conj specifically this has been asked before: https://mcmap.net/q/1777743/-interface-which-contains-conj/625403.Thermoluminescent
@Thermoluminescent anyway... an interface is not a protocol :PAlltime
Yes, but "no protocol, it's an interface" is obviously an answer to the same question.Thermoluminescent
F
6

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.

Fons answered 21/7, 2017 at 22:59 Comment(5)
It's a shame that clojure atlas doesn't seem to exist anymore. I found a class/interface hierarchy for clojure/java but I cant remember where.Alltime
@Alltime Ah, I hadn't noticed; that is indeed unfortunate! It looks like you can still get to its content, though.Fons
haha, I canged the conj when I pasted into stackoverflow. Decided that I would save a couple of cpu cycles (and avoid an instance test) ;) The get call was because of shadowing. If you move to condition to arity-3 you risk the case where insert-fn does not require it to be a IPersistentCollection...Alltime
consider (invert-many-to-one #(transient #{}) conj! {1 1 3 1})Alltime
@Alltime Good point! I didn't think about that. I've added something of a disclaimer to the end of my answer.Fons

© 2022 - 2024 — McMap. All rights reserved.