tutorial online guide example clojure

online - Clojure: Agregar funciones a defrecord sin definir un nuevo protocolo



clojure vs scala (3)

Excelente pregunta.

Como de costumbre, hay una manera hermosa de hacer las cosas en Clojure: aquí se explica cómo implementar su propio sistema simple y dinámico de OO (incluyendo herencia, polimorfismo y encapsulación) en 10 líneas de Clojure.

La idea: puede colocar funciones dentro de los mapas normales de Clojure o los registros, si lo desea, creando una estructura similar a OO. A continuación, puede utilizar esto en un estilo "prototipo".

; 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"

Estoy acostumbrado a OO en python / java. Haciendo Clojure ahora. Me encontré con defrecord, pero parece que tengo que definir un protocolo para cada función o conjunto de funciones que quiero que el registro implemente. Crear un nuevo protocolo crea fricción. Tengo que nombrar no solo la función que quiero, sino también el protocolo. Lo que estoy buscando es una forma de asociar "bien" una función con un registro para que la función tenga acceso a los parámetros del registro a través de este parámetro, sin tener que definir un nuevo protocolo o agregar una función a un protocolo existente.


Si aún no ha probado los multimethods múltiples, pueden estar más cerca de lo que está buscando.

Definir:

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

Utilizar:

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

La implementación de método múltiple que se elige se basa en la función de envío en defmulti (una función que toma los argumentos pasados ​​a la función y devuelve un valor de envío). Muy a menudo, "clase" es la función de envío, como aquí, para enviar el tipo. Multimethods admite múltiples jerarquías de tipos independientes ad-hoc o basadas en Java, implementaciones predeterminadas, etc.

Sin embargo, en general, creo que quizás desee dar un paso atrás y considerar si realmente desea protocolos o métodos múltiples. Pareces estar intentando "hacer OO" en Clojure. Si bien los aspectos de la OO (como el polimorfismo) son excelentes, tal vez debería intentar pensar de manera alternativa sobre su problema. Por ejemplo, en el ejemplo que acabo de dar, no hay una razón convincente (aún) para implementar get-name polimórficamente. ¿Por qué no solo decir:

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

¿Incluso necesitas un registro de Persona? ¿Bastaría un simple mapa? A veces las respuestas son sí, a veces no.

En general, Clojure no proporciona herencia de clase. Ciertamente, puedes crear un equivalente (incluso con protocolos) si realmente lo quieres, pero en general encuentro que hay otras formas mejores de resolver ese problema en Clojure.


Usando la idea de mikera, desarrollé una forma de tener esto (clases similares a OO)

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

Ejemplo completo, con una forma "decente y organizada" de declarar una clase similar a 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)