2017-02-13 19 views
1

如何编写一个defprotocol(和defrecord来实现它),该方法声明一个与现有函数同名的方法,并动态调度到协议/记录的方法iff我用协议/记录的一个实例调用它,但否则调度到现有的函数?使Clojure的defprotocol在现有功能下可以很好(多态)

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

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

在这一点上,我已经从得到一些预感推回编译器:

警告:协议# '用户/ SizeOps被重写功能*
警告:*已指:#' clojure.core/*在名称空间:用户,被替换的条件:#'用户/ *

然后执行:

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

,编译好了,但是当我尝试使用它,它知道只有*是一个在我的协议:

(* (Size. 1 2) 10) 

抛出:IllegalArgumentException没有执行方法:: *协议:#'user/SizeOps找到类: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 {:宽度10:高20}

但我得到的如果我稍后尝试致电(* 3 4),则同样为IllegalArgumentException。我可以在我的defrecord实施中使用命名空间clojure.core/*,但我希望我的用户能像往常一样在我的Size记录以及Long,Double等上调用*

类似Q &答:

  • 5438379(* "!" 3)"!!!",但掩盖了核心的*就像在我的例子
  • 6492458:不包括像核心功能与*操作员工作像Python扩展String(ns user (:refer-clojure :exclude [*]))避免了“覆盖”警告,但也避免了该功能:(
  • 1535235:同样,用手势向使用多方法,但细节

我怀疑正确的解决之道在于像defmultidefmethoddeftype/derive下级调度功能的地方,但我不是超级熟悉的细微差别Clojure的runtime polymorphism。而且我要拥有Size一大堆,PointRectangleCircle等,类型,每个支持+某个子集,-*/操作,所以我很想知道,如果有办法告诉defprotocol参与/建立任何现有函数的多态性,而不是简单地覆盖它们。

回答

3

在这样的情况下,当你遇到自己的协议限制时,它可以帮助创建一个单独的函数,它可以简单地调用其某些功能的协议方法,并且完成需要的其余部分使用给予定期defn S上的附加功能实现:

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

有几个优势,具体做法我已经采取了这里:

  • 所有路径,除了两个参数的路径只是使用arity dispatch(快速),并且双参数路径仅另外使用协议调度(我认为其速度与您通常要获取的内容一样快)
  • 您保留所有的arities,所以行为应该是相同的到clojure.core/*为常规旧号码

随意根据需要优化任何这些。

最后,以证明:

(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 

希望充分符合人体工程学,如先前提到的。

相关问题