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

使用Clojure协议实现自定义数据结构,clojure,Clojure,我可能错过了关于协议的全部要点,但我的问题是,协议是否可以用来指示如何迭代自定义数据结构,或者println如何打印对象 假设一张有两个向量的地图 {:a [] :b []} 当第一次调用它时,我想从:a向量中取,但是当在这个结构上连接时,我想连接到:b。我可以使用协议来实现这种行为吗?有些东西在Clojure中仍然作为Java接口实现;我想说的是,其中一些可能会永远保持这种方式,以便于与来自其他JVM语言的Clojure代码进行协作 幸运的是,在使用deftype定义类型时,您可以让新类型实

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

假设一张有两个向量的地图

{:a [] :b []}

当第一次调用它时,我想从:a向量中取,但是当在这个结构上连接时,我想连接到:b。我可以使用协议来实现这种行为吗?

有些东西在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
的内联实现。对于使用自定义
toString
的打印表示,您可以跳过它并将
seq
方法替换为返回
(seq a)
<但是,code>str始终使用
toString


如果您需要
pr
系列函数的自定义行为(包括
println
等),则必须考虑为您的类型实现自定义
打印方法<代码>打印方法
是在
clojure.core
中定义的一种多方法;看看Clojure的源代码中的示例实现。

我在玩自定义集合,想自定义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输出实现它。

还没有,因为几乎没有低级别的fn(reduce除外)被“协议化”但是您可以使用defrecord或deftype来定义一个数据类型,该数据类型的行为符合您的意愿。是的,但是我还必须实现作用于该数据类型的函数,创建冗余函数名称,例如head,它作为我的数据结构的第一个名称。否?如果您使用deftype,您可以为Clojure使用的各种接口提供实现(比如clojure.lang.ISeq)做你想做的事。