0

我有一个名为的协议IExample,我定义了一个A实现它的记录类型:

(defprotocol IExample
  (foo [this] "do something")
  (bar [this] "do something else"))

(defrecord A [field1 field2]
  IExample
  (foo [this]
    (+ field1 field2))
  (bar [this]
    (- field1 field2)))

假设我想扩展另一种(基本)类型B来实现这个协议,但我知道如何从 toB转换A

 (defn B-to-A
   "converts a B object to an A object"
   [Bobj] ...)

因为我有这个转换,我可以通过委派他们将所有协议调用IExample委托BIExample协议A

(extend B
  IExample {
    :foo (fn [this] (foo (B-to-A this)))
    :bar (fn [this] (bar (B-to-A this)))})

然而,这似乎是大量不符合 clojure 习惯的样板文件(尤其是对于更大的协议)。

我如何告诉 clojure 只是隐式转换BA每次IExampleB对象上调用函数时,使用该B-to-A函数?

4

2 回答 2

2

就样板而言,您可以编写一些宏来为您编写所有样板。另一方面,您可以在这里重新审视您的设计。

我们这里有 3 个东西(类型)ABIExample. 然后我们在这些事物之间有 2 个关系:1) a-to-example : A -> IExample2)b-to-a : B -> A并且由此我们可以通过使用组合 ie 得到第 3 个关系compose b-to-a with a-to-example : B -> IExample。现在,如果我们尝试将此设计转移到协议上,我们会发现它不是简单的转换,因为协议不会像上面设计中讨论的那样直接组合,而是我们可以使用IToExample如下所示的中间协议:

(defprotocol IExample
  (foo [this] "do something")
  (bar [this] "do something else"))

(defprotocol IToExample
  (to-example [this] "convert to IExample"))

(defrecord A [field1 field2]
  IExample
  (foo [this]
    (+ field1 field2))
  (bar [this]
    (- field1 field2))
  IToExample
  (to-example [this] this))

(deftype B [])
(defn b-to-a [b] (A. ....))
(extend B
  IToExample {:to-example b-to-a})

我们所做的就是-> IExample在我们的设计中将其表示为IToExample具有一个功能的协议。所以我们得到了:

  • a-to-example : A -> IExample通过为 A 实现 IToExample
  • b-to-a : B -> A通过正常功能
  • compose b-to-a with a-to-example : B -> IExample通过为 B 实现 IToExample 并使用 b-to-a。
于 2013-10-28T16:03:54.260 回答
1

这取决于。如果您查看 clojure 核心 seq 函数,您可能会注意到该ISec接口只有 4 个方法,并且整个“公共”序列库由(更多)调用函数定义(some-internal-function (seq argument))- 并且它们往往被明确记录为做那个也是。从概念上讲,有一个类似于您的 IExample 接口的协议,以及一个描述seq从某种类型转换为实现的函数的附加协议ISeq

如果数据类型只需要实现几个方法(因此 IExample 可以很小)并且作用于协议的算法数量很大(因为您可以按照常规函数编写所有这些方法),这是一个特别有用的策略。

于 2013-10-28T10:55:05.097 回答