我可能已经错过了关于协议的全部观点,但我的问题是,协议可用于指示如何迭代自定义数据结构或println如何打印对象?
假设有两个向量的地图,
{:a [] :b []}
当我第一次调用它时,我想从:一个向量中取出但是当我想要结合这个结构时,我想联合:b。我可以使用协议来实现这种行为吗?
答案 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-method
。 print-method
是clojure.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输出实现它。