如何将Clojure协议扩展到另一个协议?
假设我有两个协议:如何将Clojure协议扩展到另一个协议?,clojure,protocols,abstraction,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可能扩展到的所有可能类,甚至是其他人可
(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>
(g“abc”“abcd”)
=>#协议不是类型,不支持继承。协议本质上是函数定义的命名集合(以及调用这些函数时的分派机制)
如果有多个类型恰好具有相同的实现,则可以简单地调用一个公共函数。或者,您可以创建一个方法映射,并使用该映射扩展每个类型。例如:
(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)
(DEFP协议)
(a[p])
(b[p]))
(deftype R[]))
(deftype S[])
(灵活类型T[])
(def通用-P-impl
{:a(fn[p]:do-a)
:b(fn[p]:do-b})
(扩展R)
P公共-P-impl)
(续)
P公共-P-impl)
(扩展T
P公共-P-impl)
如果您提供有关实际场景的更多详细信息,我们可能会建议正确的方法。在我看来,您可以按照f
实现功能g
。如果是这种情况,那么您就拥有了不使用协议B
所需的所有多态性
我的意思是,假设f
是多态的,那么
(defn g [x y]
(* (f x) (f y)))
生成一个函数g
,该函数支持实现协议a
的所有类型
通常,当协议处于最底层时,仅根据协议函数(或在自己使用协议的其他函数上)定义的简单函数会使整个名称空间/库/程序具有多态性、可扩展性和灵活性
序列库就是一个很好的例子。简化后,有两个多态函数,first
和rest
。序列库的其余部分是普通函数。虽然我不完全理解您要做的事情,但我想知道clojure multimethods是否是解决您问题的更好的解决方案。据我所见,协议确实可以扩展协议。
我在这里举了一个例子:
在本例中,我们有一个Animalia
协议(允许其成员做dream
),该协议由Erinaceinae
协议扩展(允许其成员快速
)
我们有一个记录Hedgehog
,它是Erinaceinae
协议的一部分。我们的记录实例可以是梦想
和快速
(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.
在“Clojure应用”中,有一个按协议扩展协议的方法
(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)))))
书上说这是不可能的。我和亚历克斯·米勒谈过这件事,他说:
(g "abc" "abcd")
=> #<IllegalArgumentException java.lang.IllegalArgumentException:
No implementation of method: :g of protocol: #'user/B found for
class: java.lang.String>
这并不是在所有情况下都有效。该协议生成一个Java接口,您可以肯定地将协议扩展到该接口。
问题在于,并非每个协议实现都实现了该接口-仅使用内联声明(如(defrect Foo[a]TheProtocol(Foo…)
)实现该接口的记录或类型。如果您使用扩展类型
或扩展协议
实现协议,则协议接口的扩展不会捕获这些实例。所以,你真的不应该这么做:)
谢谢我认为这是在我的例子中最好的方法——与序列库的类比在这里很有效!这也是我的解决方案-我认为可以利用
map
来消除重复,例如(map#(extend%P common-P-impl)[R S T])
对不起,doseq
,或者应该使用封闭的doall
,因为map是懒惰的。。