clojure - 使 Clojure 的 defprotocol 与现有函数很好地(多态地)配合使用

标签 clojure operator-overloading clojurescript

如何写 defprotocol (和 defrecord 来实现它)声明一个与现有函数同名的方法,并动态分派(dispatch)给协议(protocol)/记录的方法,前提是我用协议(protocol)/记录的实例调用它,否则分派(dispatch)给现有的功能?

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

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

在这一点上,我已经从编译器中得到了一些不祥的回击:

Warning: protocol #'user/SizeOps is overwriting function *
WARNING: * already refers to: #'clojure.core/* in namespace: user, being replaced by: #'user/*



然后实现:
(defrecord Size [width height]
  SizeOps
  (* [this factor] (Size. (* width factor) (* height factor))))

编译没问题,但是当我尝试使用它时,唯一的 *它知道我的协议(protocol)中的那个:
(* (Size. 1 2) 10)

IllegalArgumentException No implementation of method: :* of protocol: #'user/SizeOps found 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实现,但我希望我的用户能够调用 *在我的 Size记录以及 Long , Double等,像往常一样。

类似问答:
  • 5438379 : 延长 String*像 Python 一样工作的运算符:(* "!" 3)"!!!" , 但掩盖了核心的 *就像我的例子
  • 6492458 : 不包括 (ns user (:refer-clojure :exclude [*])) 等核心功能避免了“覆盖”警告,但也避免了该功能:(
  • 1535235 : 相同,有使用多方法的姿态,但没有细节

  • 我怀疑正确的解决方案在于较低级别的调度功能,如 defmultidefmethoddeftype/derive但我对 Clojure 的 runtime polymorphism 的细微差别不太熟悉.而且我会有一大堆Size , Point , Rectangle , Circle等,每个类型都支持+ 的某个子集, - , * , /操作,所以我很想知道是否有办法告诉 defprotocol参与/构建任何现有函数的多态性,而不是简单地覆盖它们。

    最佳答案

    在这种情况下,当您遇到协议(protocol)本身的限制时,它可以帮助创建一个单独的函数,该函数简单地调用协议(protocol)方法以实现其某些功能,并使用附加的完成需要完成的其余部分赋予常规 defn 的功能年代:

    (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 调度(速度很快),而两个参数路径只另外使用协议(protocol)调度(我认为这和你通常会得到的一样快)做)
  • 你保留所有的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
    

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

    关于clojure - 使 Clojure 的 defprotocol 与现有函数很好地(多态地)配合使用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42196881/

    相关文章:

    namespaces - 引用外部 clojurescript 命名空间

    clojure - 什么是 zip(函数式编程?)

    C++ 对二进制表达式 ('IOperand *' 和 'IOperand *' 的无效操作数)

    c++ - 运算符==重载不比较

    clojure - 嵌套循环语句

    clojurescript - 是否可以使用宏获取 clojurescript 文件的文件名和行号?

    design-patterns - 如何在 Clojure 中构造复杂的 "state updating functions"?

    java - 使用 Netty 和 NIO 的高并发 HTTP

    emacs - 如何使用 Emacs nREPL 选择/切换 Leiningen 配置文件?

    c++ - C++ 运算符重载中的类型推断