【问题标题】:Clojure: Adding functions to defrecord without defining a new protocolClojure:向 defrecord 添加函数而不定义新协议
【发布时间】:2011-02-17 01:36:20
【问题描述】:

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

【问题讨论】:

  • 我提供了一个明确的例子,但是......但实际上,您不希望(也不需要)以如此精确的方式复制函数式语言中的类对象。

标签: clojure


【解决方案1】:

如果您还没有尝试过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 中的调度函数(一个函数,它接受传递给函数的 args 并返回一个调度值)。很常见的“类”是分派函数,就像这里一样,按类型分派。多方法支持多个独立的 ad-hoc 或基于 Java 的类型层次结构、默认实现等。

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

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

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

一般而言,Clojure 不提供类继承。如果您真的需要,您当然可以构建一个等价物(即使使用协议),但通常我发现在 Clojure 中还有其他更好的方法可以解决该问题。

【讨论】:

    【解决方案2】:

    很好的问题。

    像往常一样,在 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"
    

    【讨论】:

    • 很好,虽然我不会称之为继承。更像是使用 john 作为原型创建玛丽。
    • 是的,这当然是一种基于原型的方法。我通常称它为基于原型的继承,与基于类的继承不同,即它们都实现了相同的继承效果,只是方法不同
    【解决方案3】:

    使用 mikera 的想法,我开发了一种方法来拥有这个(类似于 OO 的类)

    ;; -----------------------------------------
    ;; main()
    ;; -----------------------------------------
    (def p1 (newPoint 3 4))
    (def p2 (newPoint 0 0))
    
    (call p1 :getY) ;; == 4
    
    (call p1 :distance p2) ;; == 5
    

    完整的示例,以“体面且有条理”的方式声明类 OO 类

    ;; -----------------------------------------
    ;; begin Point class
    ;; -----------------------------------------
    (defrecord Point [x y methods] )
    
    (def someMethods
    
      {
       :getX (fn [this] (:x this)  )    
       :getY (fn [this] (:y this)  )   
       :distance (fn [this other] 
                    (def dx (- (:x this) (:x other)))
                    (def dy (- (:y this) (:y other)))
                    (Math/sqrt (+ (* dx dx) (* dy dy) ))
                     )
      }  
    
      )
    
    ;;
    ;; Point constructor
    ;; 
    (defn newPoint [x y]
      (Point. x y someMethods)
      )
    
    ;; -----------------------------------------
    ;; end Point class
    ;; -----------------------------------------
    
    ;; -----------------------------------------
    ;; helper to call methods
    ;; -----------------------------------------
    (defn call 
      ([obj meth] ((meth (:methods obj)) obj))
      ([obj meth param1] ((meth (:methods obj)) obj param1))
      ([obj meth param1 param2] ((meth (:methods obj)) obj param1 param2))
      )
    
    ;; -----------------------------------------
    ;; main()
    ;; -----------------------------------------
    (def p1 (newPoint 3 4))
    (def p2 (newPoint 0 0))
    
    (call p1 :getY) ;; == ((:getX (:methods p1)) p1)
    
    (call p1 :distance p2) ;; == ((:distance (:methods p1)) p1 p2)
    

    【讨论】:

      猜你喜欢
      • 2011-04-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-12-15
      • 1970-01-01
      • 1970-01-01
      • 2017-12-28
      • 1970-01-01
      相关资源
      最近更新 更多