如何将Clojure协议扩展到另一个协议?

时间:2011-11-04 02:24:12

标签: clojure protocols abstraction

假设我有两个协议:

(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 个答案:

答案 0 :(得分:9)

协议不是类型,不支持继承。协议本质上是一个命名的函数定义集合(以及调用这些函数时的调度机制)。

如果您有多个类型都碰巧具有相同的实现,您可以简单地调用一个公共函数。或者,您可以使用该地图创建方法地图和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)

如果您提供有关实际情况的更多详细信息,我们可能会建议正确的方法。

答案 1 :(得分:7)

在我看来,你可以用g来实现f函数。如果是这种情况,您将拥有所需的所有多态性,而无需协议B

我的意思是以下,假设f是多态的,那么

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

产生一个函数g,它支持所有实现协议A的类型。

通常,当协议位于最底层时,仅根据协议功能(或自身使用协议的其他功能)定义的简单功能使整个命名空间/库/程序非常多态,可扩展且灵活。

序列库就是一个很好的例子。简化后,有两个多态函数firstrest。序列库的其余部分是普通函数。

答案 2 :(得分:2)

在“Clojure applied”中有一个按协议扩展协议的方法

(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-typeextend-protocol实现协议,那么这些实例将不会被协议接口的扩展捕获。所以,你真的不应该这样做:)

答案 3 :(得分:1)

从我看来,协议确实可以扩展协议。 我在这里做了一个例子:https://github.com/marctrem/protocol-extend-protocol-example/blob/master/src/extproto/core.clj

在示例中,我们有Animalia协议(允许其成员执行dream),该协议由Erinaceinae协议扩展(允许其成员go-fast )。

我们有一个记录Hedgehog,它是Erinaceinae协议的一部分。我们的记录实例可以dreamgo-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.

答案 4 :(得分:0)

虽然我并不完全明白你想要做什么,但我想知道clojure multimethods是否能更好地解决你的问题。