Как вы можете расширить протокол Clojure на другой протокол? - PullRequest
10 голосов
/ 04 ноября 2011

Предположим, у меня есть два протокола:

(defprotocol A 
  (f [this]))

(defprotocol B 
  (g [x y]))

И я хочу распространить протокол B на все экземпляры, которые поддерживают протокол A:

(extend-protocol A 
  String 
    (f [this] (.length this)))

(extend-protocol B 
  user.A
    (g [x y] (* (f x) (f y))))

Основная мотивация состоит в том, чтобы избежать необходимости расширять B отдельно на все возможные классы, на которые может быть расширен A, или даже на неизвестные будущие классы, на которые другие люди могут расширять A (представьте, если A был частью публичного API, например).

Однако это не работает - вы получите что-то вроде следующего:

(g "abc" "abcd")
=> #<IllegalArgumentException java.lang.IllegalArgumentException: 
No implementation of method: :g of protocol: #'user/B found for 
class: java.lang.String>

Возможно ли это вообще? Если нет, есть ли разумный обходной путь для достижения той же цели?

Ответы [ 5 ]

9 голосов
/ 04 ноября 2011

Протоколы не являются типами и не поддерживают наследование. Протокол - это, по сути, именованная коллекция определений функций (и механизм диспетчеризации при вызове этих функций).

Если у вас есть несколько типов, каждый из которых имеет одинаковую реализацию, вы можете просто вызвать общую функцию. Кроме того, вы можете создать карту методов и extend каждый тип с этой картой. E.g.:

(defprotocol P
  (a [p])
  (b [p]))

(deftype R [])
(deftype S [])
(deftype T [])

(def common-P-impl
  {:a (fn [p] :do-a)
   :b (fn [p] :do-b)})

(extend R
  P common-P-impl)
(extend S
  P common-P-impl)
(extend T
  P common-P-impl)

Если вы предоставите более подробную информацию о вашем реальном сценарии, мы можем предложить правильный подход.

7 голосов
/ 04 ноября 2011

Мне кажется, что вы можете реализовать функцию g в терминах f.Если это так, у вас есть весь необходимый вам полиморфизм без протокола B.

Я имею в виду следующее, учитывая, что f является полиморфным, тогда

(defn g [x y]
  (* (f x) (f y)))

дает функцию g, которая поддерживает все типы, реализующие протокол A.

Часто, когда протоколы находятся в самом низу, простые функции, определенные только в терминах функций протокола (или для других функций, которые сами используют протокол), делают все пространство имен / библиотеку / программу очень полиморфным, расширяемым и гибким.

Библиотека последовательностей является отличным примером этого.Упрощенно, есть две полиморфные функции, first и rest.Остальная часть библиотеки последовательностей - это обычные функции.

2 голосов
/ 11 мая 2017

в «Clojure apply» есть рецепт расширения протокола по протоколу

(extend-protocol TaxedCost
  Object
  (taxed-cost [entity store]
    (if (satisfies? Cost entity)
      (do (extend-protocol TaxedCost
            (class entity)
            (taxed-cost [entity store]
              (* (cost entity store) (+ 1 (tax-rate store))))) 
          (taxed-cost entity store))
      (assert false (str "Unhandled entity: " entity)))))

на самом деле ничто не мешает вам просто расширить протокол другим

(extend-protocol TaxedCost 
  Cost
  (taxed-cost [entity store]
    (* (cost entity store) (+ 1 (tax-rate store)))))

, в то время как книга говорит, что это не таквозможный.Я говорил об этом с Алексом Миллером, и он сказал следующее:

Это действительно работает не во всех случаях.Протокол генерирует интерфейс Java, и вы наверняка можете расширить протокол для этого интерфейса.Проблема в том, что не каждая реализация протокола реализует этот интерфейс - только записи или типы, которые делают это с помощью встроенного объявления, такого как (defrecord Foo [a] TheProtocol (foo ...)).Если вы реализуете протокол с использованием extend-type или extend-protocol, эти экземпляры НЕ будут перехвачены расширением интерфейса протокола.Таким образом, вы действительно не должны этого делать:)

1 голос
/ 14 декабря 2016

Из того, что я вижу, протоколы действительно могут расширять протоколы. Я сделал пример здесь: https://github.com/marctrem/protocol-extend-protocol-example/blob/master/src/extproto/core.clj

В этом примере у нас есть протокол Animalia (который позволяет его членам делать dream), который расширен протоколом Erinaceinae (который позволяет его членам go-fast).

У нас есть запись Hedgehog, которая является частью протокола Erinaceinae. Наш экземпляр записи может как dream, так и go-fast.

(ns extproto.core
  (:gen-class))


(defprotocol Animalia (dream [this]))

(defprotocol Erinaceinae (go-fast [this]))

(extend-protocol Animalia 
  extproto.core.Erinaceinae
  (dream [this] "I dream about things."))

(defrecord Hedgehog [lovely-name]
  Erinaceinae
  (go-fast [this] (format "%s the Hedgehog has got to go fast." (get this :lovely-name))))



(defn -main
  [& args]  
  (let [my-hedgehog (Hedgehog. "Sanic")]
    (println (go-fast my-hedgehog))
    (println (dream my-hedgehog))))

;1> Sanic the Hedgehog has got to go fast.
;1> I dream about things.
0 голосов
/ 04 ноября 2011

Хотя я не до конца понимаю, что вы пытаетесь сделать, мне интересно, будет ли мультиметод clojure лучшим решением для вашей проблемы.

...