Making Clojure's defprotocol play nice (polymorphically) with existing functions
我如何编写一个
例如,我想创建一个支持基本算术的几何帮助器(在本示例中为乘法,以使其简短):
1 2 | (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/*
然后执行:
1 2 3 | (defrecord Size [width height] SizeOps (* [this factor] (Size. (* width factor) (* height factor)))) |
可以编译,但是当我尝试使用它时,它知道的唯一
1 | (* (Size. 1 2) 10) |
IllegalArgumentException No implementation of method: :* of protocol: #'user/SizeOps found for class: java.lang.Long
我可以通过在实现中完全指定核心
1 2 3 4 | (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}
但是,如果稍后尝试调用
类似的问答:
-
5438379:使用类似于Python:
(*"!" 3) 的* 运算符扩展String 吗?"!!!" ,但是像我的示例一样模糊了核心的* -
6492458:排除
(ns user (:refer-clojure :exclude [*])) 之类的核心功能可以避免出现"覆盖"警告,但也可以避免在:( - 1535235:相同,但有使用多种方法的手势,但没有详细信息
我怀疑正确的解决方案位于
在这种情况下,当您遇到协议本身的局限性时,可以帮助创建一个单独的函数,该函数简单地调用协议方法以实现其某些功能,并使用其他方法来完成其余的工作 常规
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | (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调度(这是快速的),而两参数路径仅另外使用协议调度(我认为这与您通常会为尝试的结果而获得的速度一样快) 去做)
-
您保留所有Arities,因此对于常规旧数字,其行为应与
clojure.core/* 相同
可以根据需要随意优化其中的任何一个。
最后,演示:
1 2 3 4 5 6 | (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 |
如前所述,希望它符合人体工程学。