Какой протокол определяет conj в clojure?
Допустим, я написал функцию:
(defn foo [to x] (conj to x))
и хотел бы задокументировать это, заявив, что to
должен реализовывать некоторый протокол (как в структуре / типе to
должен поддержать звонок conj
). Есть ли веб-сайт или база данных, которая имеет эту информацию? Очевидно, я хотел бы обобщить этот вопрос на "где я могу найти полную ссылку для всех протоколов clojure?"
В качестве ясного и конкретного примера с использованием предложения Сэма Эстепа это будет выглядеть так:
(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))))
1 ответ
К сожалению, протоколы не были введены до Clojure 1.2, и к тому времени все встроенные абстракции структуры данных уже были реализованы как интерфейсы Java вместо протоколов. Это имеет недостатки, которые вы ожидаете, но хотя переопределение всех этих абстракций в качестве протоколов было уместно для ClojureScript, так как он был создан после того, как протоколы были введены, было бы невозможно дооснастить их в JVM Clojure.
Если вы посмотрите на исходный код conj
вы увидите, что это вызывает clojure.lang.RT/conj
, который требует, чтобы его первый аргумент реализовывал IPersistentCollection
интерфейс. Таким образом, вы можете написать свою функцию так:
(defn foo [to x]
{:pre [(instance? clojure.lang.IPersistentCollection to)]}
(conj to x))
Для вашего обобщения я бы указал на вопрос, который я задавал в прошлом о реализации основных интерфейсов Clojure. Если ответов на ваш вопрос недостаточно, пожалуйста, дайте мне знать, и я добавлю более подробную информацию здесь.
Я бы внес несколько небольших корректировок в ваш invert-many-to-one
функция:
(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))))
- Я изменил тело arity-1, чтобы называть тело arity-2 вместо тела arity-3; таким образом, вы указываете только
conj
по умолчанию в одном месте. - Я перенес предварительное условие в тело arity-3, чтобы оно использовалось во всех случаях.
- Я заменил
clojure.core/get
простоget
за идиоматичность.
Конечно, у этих трех изменений есть свои недостатки, как вы указали в своих комментариях, так что определенно принимайте их с недоверием.