Какой протокол определяет 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 за идиоматичность.

Конечно, у этих трех изменений есть свои недостатки, как вы указали в своих комментариях, так что определенно принимайте их с недоверием.

Другие вопросы по тегам