我正在编写一个clojure函数来将各种数据类型格式化为字符串。
我天真的解决方案:
(defn p [d]
(cond
(vector? d) (str "vector: " d)
(list? d) (str "list: " d)))
#'user/p
user> (p [1 2 3])
"vector: [1 2 3]"
user> (p '(1 2 3))
"list: (1 2 3)"
我之前没有使用过多种方法。我这是一个很好的用途,还是有另一种方法来避免使用cond的臭味?
答案 0 :(得分:5)
我会按照@rodnaph的建议,定义格式协议并将其扩展到您需要的类型:
(defprotocol Format
(fmt [this]))
(extend-protocol Format
clojure.lang.IPersistentVector
(fmt [this] (str "vector:" this))
clojure.lang.IPersistentList
(fmt [this] (str "list:" this)))
但是我不知道哪个会有更好的性能,多方法或协议扩展。
多方法定义可能如下所示:
(defmulti fmt class)
(defmethod fmt
clojure.lang.IPersistentVector [this]
(str "vector:" this))
(defmethod fmt
clojure.lang.IPersistentList [this]
(str "list:" this))
编辑:您可能想要检查this question about protocols vs multimethods,因为很好地解释了两者的常见用例。根据该信息,最好在您的方案中使用协议。
答案 1 :(得分:1)
答案 2 :(得分:1)
我确信你的问题显示的是与你真正需要完成的事情相关的简化案例。对于一般解决方案,我同意协议是一种不错的方法。
你问过多方法,但是你遇到的麻烦就是调度功能。 defmulti接受一个调度函数,该函数将在实函数的参数上调用。 dispatch函数必须返回一个值,然后可以使用该值来选择将调用哪个方法实现。
麻烦的是,你发什么?要区分集合类型,您最终会得到类似的结果:
(defmulti stringify class)
(defmethod stringify clojure.lang.PersistentVector [v] ...)
(defmethod stringify clojure.lang.PersistentArrayMap [m] ...)
;;; More dispatching on concrete class names
好吧,只要你看到代码中出现特定的clojure.lang类名,就应该关闭所有类型的警钟。这些太具体了......如果Clojure核心库发生变化,它们将会破坏,它们将无法与Java互操作非常干净地工作,它们不包括碰巧实现Seqable的用户定义类型......简而言之,它们是抽象的细分。
每当你想要发送类名时,无论是来自Clojure,Java还是第三方库,你都应该总是到达extend-type。