使用Clojure协议实现自定义数据结构

时间:2010-05-31 14:18:27

标签: clojure

我可能已经错过了关于协议的全部观点,但我的问题是,协议可用于指示如何迭代自定义数据结构或println如何打印对象?

假设有两个向量的地图,

{:a [] :b []}

当我第一次调用它时,我想从:一个向量中取出但是当我想要结合这个结构时,我想联合:b。我可以使用协议来实现这种行为吗?

2 个答案:

答案 0 :(得分:13)

有些东西仍然在Clojure中作为Java接口实现;其中,有些人可能会永远保持这种方式,以便与其他JVM语言的Clojure代码协作。

幸运的是,在使用deftype定义类型时,您可以让新类型实现您需要的任何Java接口(Brian在上面的注释中提到),以及java.lang.Object的任何方法。与您的描述匹配的示例可能如下所示:

(deftype Foo [a b]
  clojure.lang.IPersistentCollection
  (seq [self] (if (seq a) self nil))
  (cons [self o] (Foo. a (conj b o)))
  (empty [self] (Foo. [] []))
  (equiv
   [self o]
   (if (instance? Foo o)
     (and (= a (.a o))
          (= b (.b o)))
     false))
  clojure.lang.ISeq
  (first [self] (first a))
  (next [self] (next a))
  (more [self] (rest a))
  Object
  (toString [self] (str "Foo of a: " a ", b: " b)))

你可以在REPL上用它做的一个例子:

user> (.toString (conj (conj (Foo. [] []) 1) 2))
"Foo of a: [], b: [1 2]"
user> (.toString (conj (conj (Foo. [:a :b] [0]) 1) 2))
"Foo of a: [:a :b], b: [0 1 2]"
user> (first (conj (conj (Foo. [:a :b] [0]) 1) 2))
:a
user> (Foo. [1 2 3] [:a :b :c])
(1 2 3)

请注意,REPL将其打印为seq;我认为这是因为clojure.lang.ISeq的内联实施。您可以跳过它,并使用自定义seq(seq a)方法替换为返回toString一个打印表示的方法。 str始终使用toString

如果您需要pr家庭功能的自定义行为(包括println等),则必须考虑为您的类型实施自定义print-methodprint-methodclojure.core中定义的多方法;在Clojure的源代码中查看core_print.clj示例实现。

答案 1 :(得分:0)

我正在玩自定义集合,并希望自定义REPL的输出,所以我最后关注了Michal的建议。我已经包含了关于如何执行此操作的代码段,因为我发现在源代码中进行筛选需要一段时间,因为我没有在其他任何地方找到此解释。

(defmethod print-method your.custom.collection.goes.Here [c, ^java.io.Writer w]
    (.write w (str "here is my custom output: " c)))

这很方便,例如,在seq始终使用括号打印自定义向量的情况下(与Michal的示例一样)并且您需要方括号,如常规Clojure向量:

(defmethod print-method your.custom.Vector [v, ^java.io.Writer w]
    (.write w (str (into [] v))))

这也意味着现在可以实现seq实际返回数据类型序列的方式,而不是仅仅为REPL输出实现它。

相关问题