Clojure: добавление функций для записи без определения нового протокола - PullRequest
20 голосов
/ 17 февраля 2011

Я привык к ОО в Python / Java.Делаю Clojure сейчас.Я столкнулся с defrecord, но, похоже, мне нужно определить протокол для каждой функции или набора функций, которые я хочу реализовать в записи.Создание нового протокола создает трения.Я должен назвать не только функцию, которую я хочу, но и протокол.То, что я ищу, это способ «красиво» связать функцию с записью, чтобы функция имела доступ к параметрам записи через этот параметр, без необходимости определять новый протокол или добавлять функцию к существующему протоколу.

Ответы [ 3 ]

21 голосов
/ 17 февраля 2011

Если вы еще не пробовали мультиметоды , они могут быть ближе к тому, что вы ищете.

Определение:

(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 (функция, которая принимает аргументы, переданные функции и возвращает значение диспетчеризации). Обычно «класс» - это функция отправки, как здесь, для отправки по типу. Мультиметоды поддерживают несколько независимых иерархий типа ad-hoc или на основе Java, реализации по умолчанию и т. Д.

В общем, я думаю, что, возможно, вы захотите сделать шаг назад и подумать, действительно ли вам нужны протоколы или мультиметоды. Вы, кажется, пытаетесь "сделать OO" в Clojure. Хотя аспекты ОО (такие как полиморфизм) велики, возможно, вам следует попробовать по-другому взглянуть на вашу проблему. Например, в только что приведенном примере нет убедительной причины (пока) для полиморфной реализации get-name. Почему бы просто не сказать:

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

Вам вообще нужна запись о человеке? Будет ли достаточно простой карты? Иногда ответы да, иногда нет.

Обычно Clojure не обеспечивает наследование классов. Вы, конечно, можете создать эквивалент (даже с протоколами), если вы действительно этого хотите, но в целом я считаю, что есть другие, более эффективные способы решения этой проблемы в Clojure.

14 голосов
/ 17 февраля 2011

Отличный вопрос.

Как обычно, есть прекрасный способ сделать что-то в Clojure - вот как реализовать собственную простую динамическую систему ОО (включая наследование, полиморфизм и инкапсуляцию) в 10 строках Clojure.

Идея: Вы можете поместить функции в карты нормалей или карты Clojure, если хотите, создавая ОО-подобную структуру.Затем вы можете использовать это в стиле «прототип».

; 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"
0 голосов
/ 28 марта 2018

Используя идею 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)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...