Clojure:在没有定义新协议的情况下向defrecord添加函数

时间:2011-02-17 01:36:21

标签: clojure

我习惯了python / java中的OO。现在做Clojure。我遇到了defrecord,但似乎我必须为我希望记录实现的每个函数或函数集定义一个协议。创建新协议会产生摩擦。我不仅要说出我想要的功能,还要说出协议。我正在寻找的是一种“很好地”将函数与记录相关联的方法,以便函数可以通过this参数访问记录的参数,而无需定义新协议或向现有协议添加函数。

3 个答案:

答案 0 :(得分:21)

如果您还没有尝试multimethods,它们可能会更接近您所寻找的内容。

定义:

(defrecord Person [first middle last])
(defmulti get-name class)
(defmethod get-name Person [person] (:first person))

使用:

(def captain (Person. "James" "T" "Kirk"))
(get-name captain)

选择的多方法实现基于defmulti中的dispatch函数(一个将传递给函数的args并返回一个调度值的函数)。通常“class”是调度函数,就像这里一样,调度类型。 Multimethods支持多个独立的ad-hoc或基于Java的类型层次结构,默认实现等。

总的来说,我想也许你可能想退一步考虑一下你是否真的需要协议或多方法。你似乎试图在Clojure中“做OO”。虽然OO(如多态)的各个方面都很棒,但也许您应该尝试以其他方式思考您的问题。例如,在我刚刚给出的示例中,没有令人信服的理由(尚未)以多态方式实现get-name。为什么不说:

(defn get-name [x] (:first x))

你甚至需要一个人物记录吗?一张简单的地图就够了吗?有时答案是肯定的,有时候没有。

一般来说,Clojure不提供类继承。如果你真的想要它,你当然可以建立一个等价物(甚至是协议)但通常我发现在Clojure中还有其他更好的方法可以解决这个问题。

答案 1 :(得分:14)

很棒的问题。

像往常一样,在Clojure中有一种很好的方法 - 这里是如何在10行Clojure中实现自己的简单动态OO系统(包括继承,多态和封装)。

想法:如果需要,可以将函数放在法线Clojure映射或记录中,从而创建类似OO的结构。然后,您可以以“原型”样式使用它。

; define a prototype instance to serve as your "class"
; use this to define your methods, plus any default values
(def person-class
  {:get-full-name 
    (fn [this] (str (:first-name this) " " (:last-name this)))})

; define an instance by merging member variables into the class
(def john 
  (merge person-class 
    {:first-name "John" :last-name "Smith"}))

; macro for calling a method - don't really need it but makes code cleaner
(defmacro call [this method & xs]
  `(let [this# ~this] ((~method this#) this# ~@xs)))

; call the "method"
(call john :get-full-name)
=> "John Smith"

; added bonus - inheritance for free!
(def mary (merge john {:first-name "Mary"}))
(call mary :get-full-name)
=> "Mary Smith"

答案 2 :(得分:0)

使用mikera的想法,我开发了一种方法(类似OO的类)

Set

完整的例子,有一个体面和有组织的"声明类似OO的类

的方法
;; -----------------------------------------
;; main()
;; -----------------------------------------
(def p1 (newPoint 3 4))
(def p2 (newPoint 0 0))

(call p1 :getY) ;; == 4

(call p1 :distance p2) ;; == 5