2010-05-31 78 views
10

我可能错过了关于协议的全部观点,但我的问题是,可以使用协议来规定如何迭代自定义数据结构或println如何打印对象?使用Clojure协议实现自定义数据结构

假设具有两个向量地图,

{:a [] :b []} 

当称作第一上它,我想从采取:一个矢量,但是,该结构当连词我想连词为:b。我可以使用协议来实现这种类型的行为吗?

+0

还没有,因为几乎没有低级别的fn(除了reduce)已经被“协议化”了,但是你可以使用defrecord或者deftype来定义一个你想要的行为。 – cgrand 2010-05-31 15:18:12

+0

是的,但是我必须实现作用于它的函数,创建多余的函数名称,例如作为我的数据结构的第一个函数头,否? – 2010-05-31 15:42:35

+0

如果您使用deftype,您可以为Clojure使用的各种接口(如clojure.lang.ISeq)提供您想要的实现。 – Brian 2010-05-31 17:16:18

回答

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打印它作为一个序列;我相信这是因为内联实施了clojure.lang.ISeq。您可以跳过它,并使用自定义toString代替seq方法,其中一个返回(seq a)作为打印表示法。虽然,str总是使用toString

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

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总是用括号打印您的自定义矢量(与米哈尔的例子)的情况下,你想方括号像普通Clojure的载体:

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

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