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

11

假设我有两个协议:

(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个回答

10
协议不是类型,也不支持继承。协议本质上是一组函数定义的命名集合(以及在调用这些函数时的分派机制)。
如果您有多个类型恰好具有相同的实现,则可以简单地调用通用函数。或者,您可以创建一个方法映射,并使用该映射对每个类型进行扩展。例如:
(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))
如果您提供有关实际情况的更多详细信息,我们可能能够建议正确的方法。

这也是我的解决方案 - 我认为可以利用 map 来消除重复,例如 (map #(extend % P common-P-impl) [R S T]) - KingCode
抱歉,应该使用doseq或包含doall来处理地图的惰性。 - KingCode

7

我认为您可以通过f实现函数g。如果是这样,那么您就无需使用协议B即可拥有所需的多态性。

我的意思是,如果f是多态的,那么:

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

产生一个函数g,它支持所有实现协议A的类型。
通常,在协议位于底层时,仅基于协议函数定义的简单函数(或其他使用该协议的函数)使整个命名空间/库/程序非常多态化、可扩展和灵活。
序列库是一个很好的例子。简化起见,有两个多态函数firstrest。序列库的其余部分是普通函数。

谢谢。我认为这是在我的情况下最好的方法 - 序列库的类比在这里很有效! - mikera

3
在《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)))))

虽然书上说这是不可能的。我与Alex Miller讨论过这个问题,他说:

在所有情况下都不能实现。该协议生成一个Java接口,你可以将协议扩展到该接口。 问题是,并非每个协议实现都会实现该接口-只有通过内联声明 ((defrecord Foo [a] TheProtocol (foo ...))) 实现的记录或类型才会这样做。如果您使用 extend-typeextend-protocol 实现协议,则这些实例将不会被协议接口的扩展所捕获。因此,你真的不应该这么做 :)


1
从我所看到的,协议确实可以扩展协议。 我在这里举了一个例子: 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

虽然我不完全理解你想做什么,但我想知道Clojure多方法是否是你问题的更好解决方案。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接