将自定义行为添加到Clojure的序列中

时间:2011-09-16 02:45:27

标签: clojure polymorphism abstraction

Clojure如此强大的部分原因是所有核心数据类型都实现了相同的序列抽象:clojure.lang.ISeq。

这意味着“first”,“concat”,“cons”,“map”,“rest”等功能一般都适用于所有这些数据类型。

我的问题是:我如何将自己的自定义函数添加到混合中,并使其适用于从ISeq扩展的所有类型?

第一次尝试是定义我自己的协议,然后“(扩展类型clojure.lang.ISeq ...”,但这不起作用(它编译但不会将行为添加到实际类型)另一个想法是编写一个宏,在所有Clojure类型(PersistentHashMap,PersistentList等)上显式地执行“扩展类型”,但这看起来很笨拙。

有没有优雅/惯用的方法呢?多方法也许?

3 个答案:

答案 0 :(得分:8)

你究竟想做什么?

如果您尝试向现有类型添加行为:编写处理seqs的常规函数​​或使用多方法或extend执行您想要的操作。

另外需要注意的是,大多数Clojure“序列”类型(向量,集合,映射)本身并不是序列(它们不实现clojure.lang.ISeq),因此您必须做的不仅仅是添加到clojure.lang.ISeq如果您希望支持他们。

答案 1 :(得分:3)

Stuart Sierra的一篇IBM Developerworks文章标题为Solving the Expression Problem with Clojure 1.2,可能会提供您的问题的见解和答案。

它使用协议在几种数据类型上定义一组函数,并使用extend扩展现有类(数据类型),以便它们可以使用这些函数。这可能不是你想要的,但它可能是解决问题的一种方法。

它还向您展示了如何定义实现现有协议/接口的自定义数据类型(使用defrecorddeftype)。

答案 2 :(得分:3)

执行此操作的最佳方法是使用现有通用Clojure函数编写新函数,以正确处理不同的数据类型。

此类通用功能的示例:

  • into将项目附加到任何类型的集合
  • empty返回与其参数
  • 相同类型的空集合

然后你可以编写自己的泛型函数来利用它们,例如:

(defn take-every-other [coll]
  (into 
    (empty coll)
    (map first (partition 2 coll))))

 (take-every-other [1 2 3 4 5 6])
 => [1 3 5]

 (take-every-other {:a 1 :b 2 :c 3 :d 4})
 => {:a 1, :c 3}

如果您仍需要更多通用功能,可以随时深入了解Clojure源代码,了解这些函数的编写方式。