使Clojure的defprotocol与现有函数一起使用(多态)

时间:2017-02-13 04:53:39

标签: clojure operator-overloading clojurescript

如何编写一个defprotocol(和defrecord来实现它),声明一个与现有函数同名的方法,并动态调度到协议/记录的方法 iff 我用协议/记录的实例调用它,否则调度到现有函数?

例如,我想创建一个支持基本算术的几何助手(在这个例子中只是乘法,以保持简短):

(defprotocol SizeOps
  (* [this factor] "Multiply each dimension by factor and return a new Size"))

此时我已经从编译器中得到了一些不祥的预感:

  

警告:协议#' user / SizeOps正在覆盖功能*
  警告:*已经引用:命名空间中的#' clojure.core / *:user,替换为:#' user / *

然后执行:

(defrecord Size [width height]
  SizeOps
  (* [this factor] (Size. (* width factor) (* height factor))))

编译好了,但是当我尝试使用它时,它所知道的唯一*是我协议中的那个:

(* (Size. 1 2) 10)
  

IllegalArgumentException没有方法的实现:: * of protocol:#' user / SizeOps for class:java.lang.Long

我可以通过在我的实现中完全指定核心*函数来解决这个问题:

(defrecord Size [width height]
  SizeOps
  (* [this factor] (Size. (clojure.core/* width factor) (clojure.core/* height factor))))
(* (Size. 1 2) 10)
  

#user.Size {:width 10,:height 20}

但如果我稍后尝试拨打IllegalArgumentException,我会得到相同的(* 3 4)。我可以在我的clojure.core/*实现中使用命名空间defrecord,但我希望我的用户能够在我的*记录以及{{1}上调用Size像往常一样。},Long等。

类似的Q& A:

  • 5438379:使用Double运算符扩展String,其工作方式与Python类似:*(* "!" 3),但模糊了核心{{1}就像在我的例子中一样
  • 6492458:排除"!!!"等核心功能,避免覆盖"覆盖"警告,但也避免具有该功能:(
  • 1535235:同样,有使用多方法但没有细节的姿态

我怀疑正确的解决方案位于较低级别的调度功能中,例如*(ns user (:refer-clojure :exclude [*]))defmulti / defmethod,但我并不十分熟悉Clojure runtime polymorphism的细微差别。而且我将拥有大量deftypederiveSizePoint等类型,每种类型都支持{{1}的某些子集},RectangleCircle+操作,所以我很想知道是否有方法告诉-参与/建立任何现有函数的多态性,而不是简单地覆盖它们。

1 个答案:

答案 0 :(得分:4)

在这种情况下,当你遇到协议本身的限制时,它可以帮助创建一个单独的函数,只需要调用协议方法来实现它的某些功能,并完成剩下的工作。使用常规(ns example.size (:refer-clojure :exclude [*]) (:require [clojure.core :as clj])) (defprotocol SizeOps (times [this factor])) (extend-protocol SizeOps Object (times [this factor] (clj/* this factor))) (defrecord Size [width height] SizeOps (times [this factor] (->Size (clj/* width factor) (clj/* height factor)))) (defn * ([] (clj/*)) ([x] (clj/* x)) ([x y] (times x y)) ([x y & more] (apply clj/* x y more))) 的附加功能:

clojure.core/*

我在这里采取的具体方法有几个好处:

  • 除了双参数路径之外的所有路径只使用arity dispatch(快速),而双参数路径另外只使用协议调度(我认为这个速度和你通常会得到的一样快'试图做)
  • 你保留所有的arities,所以对于常规旧数字,行为应该与(ns example.core (:refer-clojure :exclude [*]) (:require [example.size :refer [* ->Size]])) (* (->Size 1 2) 10) ;=> #example.size.Size{:width 10, :height 20} (* 3 4) ;=> 12 相同

根据需要随意优化其中任何一项。

最后,要证明:

%dw 1.0
%output application/json
%var contactLookup = payload groupBy $.personId
---
flowVars.PersonCursor map {
    ($),
    contacts : contactLookup[$.personId]
}

希望足够符合人体工程学,如前所述。