【问题标题】:How can you extend a Clojure protocol to another protocol?如何将 Clojure 协议扩展到另一个协议?
【发布时间】:2011-12-21 16:47:58
【问题描述】:

假设我有两个协议:

(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>

这可能吗?如果没有,是否有合理的解决方法来实现相同的目标?

【问题讨论】:

    标签: clojure protocols abstraction


    【解决方案1】:

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

    如果你有多个类型碰巧都有相同的实现,你可以简单地调用一个通用函数。或者,您可以使用该映射创建一个方法映射和extend 每种类型。例如:

    (默认协议 P (一个 [p]) (b [p])) (定义类型 R []) (定义类型 S []) (定义类型 T []) (def common-P-impl {:a (fn [p] :do-a) :b (fn [p] :do-b)}) (扩展 R P common-P-impl) (扩展 S P common-P-impl) (扩展 T P common-P-impl)

    如果您提供更多关于您的实际场景的详细信息,我们或许能够建议正确的方法。

    【讨论】:

    • 这也是我的解决方案 - 我认为 map 可以用来删除重复,例如(map #(extend % P common-P-impl) [R S T])
    • 抱歉,doseq 或封闭的doall 应该使用 re: map 是懒惰的..
    【解决方案2】:

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

    我的意思是以下,鉴于f是多态的,那么

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

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

    通常,当协议处于最底层时,仅根据协议函数(或其他本身使用协议的函数)定义的简单函数会使整个命名空间/库/程序非常多态、可扩展和灵活。

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

    【讨论】:

    • 谢谢。我认为这对我来说是最好的方法 - 与序列库的类比在这里很有效!
    【解决方案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)))))
    

    虽然书上说这是不可能的。我和亚历克斯米勒谈过这件事,他说:

    它确实不适用于所有情况。该协议生成一个 Java 接口,您当然可以将协议扩展到该接口。 问题是不是每个协议实现都实现了该接口——只有记录或类型使用像(defrecord Foo [a] TheProtocol (foo ...)) 这样的内联声明来实现。如果您使用extend-typeextend-protocol 实现协议,那么这些实例将不会被协议接口的扩展捕获。所以,你真的不应该这样做:)

    【讨论】:

      【解决方案4】:

      在我看来,协议确实可以扩展协议。 我在这里做了一个例子: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.
      

      【讨论】:

        【解决方案5】:

        虽然我不完全理解您要做什么,但我想知道 clojure 多方法是否会更好地解决您的问题。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-08-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-11-30
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多