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

yal*_*lis 20 clojure

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

Ale*_*ler 21

如果你还没有尝试多方法的是,他们可能更接近你在找什么.

限定:

(defrecord Person [first middle last])
(defmulti get-name class)
(defmethod get-name Person [person] (:first person))
Run Code Online (Sandbox Code Playgroud)

使用:

(def captain (Person. "James" "T" "Kirk"))
(get-name captain)
Run Code Online (Sandbox Code Playgroud)

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

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

(defn get-name [x] (:first x))
Run Code Online (Sandbox Code Playgroud)

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

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


mik*_*era 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"
Run Code Online (Sandbox Code Playgroud)

  • 很好,虽然我不会称之为继承.更像是使用john作为原型来创建mary. (4认同)
  • 是的,它肯定是基于原型的方法.我通常将它称为基于原型的继承,与基于类的继承不同,即它们都通过不同的方法实现相同的继承效果 (3认同)