oop clojure lisp common-lisp clos

oop - CLOS para Clojure?



lisp common-lisp (7)

Clojure en sí no tiene un sistema de objetos, por dos razones:

  1. Clojure está diseñado específicamente para ser alojado en una plataforma orientada a objetos y simplemente absorbe el sistema de objetos de la plataforma subyacente. Es decir, ClojureJVM tiene el sistema de objetos JVM, ClojureCLR tiene el sistema de objetos CLI, ClojureScript tiene el sistema de objetos ECMAScript, y así sucesivamente.
  2. Rich Hickey odia los objetos.

Pero, obviamente, puede implementar un sistema de objetos en Clojure. Clojure es, después de todo, Turing-completo.

Mikel Evins está trabajando en un nuevo enfoque de OO al que llama Categorías . Tiene implementaciones para varios Lisps, incluido Clojure (aunque no se garantiza que todos los puertos estén actualizados todo el tiempo).

Las categorías están siendo subsumidas lentamente por Bard , un nuevo dialecto Lisp que Mikel está diseñando, que tiene Categorías integradas. (Lo que, a su vez, puede convertirse en el lenguaje de implementación de Closos , una idea que Mikel tenía sobre cómo diseñar un sistema operativo. )

¿Existe algo como CLOS (Common Lisp Object System) para Clojure?


Clojure no tiene CLOS y no quiere CLOS pero puede implementarlo.

Clojure quiere ser inmutable por lo que tener OO mutable sería un poco estúpido, pero puedes tener un tipo de OO.

Con estas tres cosas, debería poder satisfacer todas sus necesidades, pero la mayoría de las veces, es mejor usar solo las funciones normales y las estructuras de datos estándar.


El uso del paradigma OO es ideal para escribir código acoplado, burlarse y probar. Clojure hace que esto sea tan fácil de lograr.

Un problema que había encontrado en el pasado era el código dependiendo de otro código. Los espacios de nombres de Clojure en realidad exacerban el problema si no se usan bien. Idealmente, los espacios de nombres se pueden simular, pero como he encontrado ... hay muchos problemas con los espacios de nombres de burla:

https://groups.google.com/forum/?fromgroups=#!topic/clojure/q3PazKoRlKU

Una vez que comienzas a crear aplicaciones cada vez más grandes, los espacios de nombres comienzan a depender unos de otros y se vuelve muy desagradable probar tus componentes de nivel superior por separado sin tener un montón de dependencias. La mayoría de las soluciones involucran la función de reencuadernación y otras funciones de magia negra, pero el problema es que el tiempo de las pruebas, las dependencias originales todavía se están cargando, lo que se convierte en un gran problema si tiene una gran aplicación.

Me motivaron a buscar alternativas después de usar las bibliotecas de bases de datos. Las bibliotecas de bases de datos me han traído mucho dolor: tardan años en cargarse y, por lo general, son el núcleo de su aplicación. Es muy difícil probar su aplicación sin traer una base de datos completa, la biblioteca y los periféricos asociados a su código de prueba.

Desea poder empaquetar sus archivos para que se puedan "intercambiar" partes de su sistema que dependen de su código de base de datos. La metodología de diseño OO proporciona la respuesta.

Lo siento, la respuesta es bastante larga ... Quería dar una buena explicación de por qué el diseño OO se usa más que la forma en que se usa. Así que se tuvo que usar un ejemplo real. He intentado mantener las declaraciones ns para que la estructura de la aplicación de ejemplo se mantenga lo más clara posible.

código de estilo de clojure existente

Este ejemplo utiliza carmine , que es un cliente redis. Es relativamente fácil trabajar con él y es rápido para iniciarlo en comparación con korma y datomic, pero una biblioteca de base de datos sigue siendo una biblioteca de base de datos:

(ns redis-ex.history (:require [taoensso.carmine :as car] [clojure.string :as st])) (defmacro wcr [store kdir f & args] `(car/with-conn (:pool ~store) (:conn ~store) (~f (st/join "/" (concat [(:ns ~store)] ~kdir)) ~@args))) (defn empty [store kdir] (wcr store kdir car/del)) (defn add-instance [store kdir dt data] (wcr store kdir car/zadd dt data)) (defn get-interval [store kdir dt0 dt1] (wcr store kdir car/zrangebyscore dt0 dt1)) (defn get-last [store kdir number] (wcr store kdir car/zrange (- number) -1)) (defn make-store [pool conn ns] {:pool pool :conn conn :ns ns})

código de prueba existente

todas las funciones deben ser probadas ... esto no es nada nuevo y es un código estándar de clojure

(ns redis-ex.test-history0 (:require [taoensso.carmine :as car] [redis-ex.history :as hist])) (def store (hist/make-store (car/make-conn-pool) (car/make-conn-spec) "test")) (hist/add-instance store ["hello"] 100 100) ;;=> 1 (hist/get-interval store ["hello"] 0 200) ;;=> [100]

mecanismo de despacho orientado a objetos

La idea de que ''OO'' no es malvada pero en realidad me fue muy útil después de ver esta charla Misko Hevery:

http://www.youtube.com/watch?v=XcT4yYu_TTs

La idea básica es que si desea crear una aplicación grande, debe separar la ''funcionalidad'' (las entrañas del programa) del ''cableado'' (las interfaces y las dependencias). Cuantas menos dependencias mejor.

Uso clojure hash-maps como ''objetos'' porque no tienen dependencias de biblioteca y son completamente genéricos (vea a Brian Marick hablando sobre el uso del mismo paradigma en ruby ​​- http://vimeo.com/34522837 ).

Para hacer que su código de clojure esté ''orientado a objetos'', necesita la siguiente función: ( send robado de smalltalk), que simplemente distribuye una función asociada con una clave en un mapa si está asociada con una clave existente.

(defn call-if-not-nil [f & vs] (if-not (nil? f) (apply f vs)) (defn send [obj kw & args] (call-if-not-nil (obj kw) obj))

Proporciono la implementación en una biblioteca de utilidad de propósito general (https://github.com/zcaudate/hara en el espacio de nombres hara.fn ). Son 4 líneas de código si quieres implementarlo por ti mismo.

definiendo el objeto ''constructor''

ahora puede modificar la función original de make-store para agregar funciones en el mapa. Ahora tienes un nivel de direccionamiento indirecto.

;;; in the redis-ex.history namespace, make change `make-store` ;;; to add our tested function definitions as map values. (defn make-store [pool conn ns] {:pool pool :conn conn :ns ns :empty empty :add-instance add-instance :get-interval get-interval :get-last get-last})

;;; in a seperate test file, you can now test the ''OO'' implementation (ns redis-ex.test-history1 (:require [taoensso.carmine :as car] [redis-ex.history :as hist])) (def store (hist/make-store (car/make-conn-pool) (car/make-conn-spec) "test")) (require ''[hara.fn :as f]) (f/send store :empty ["test"]) ;; => 1 (f/send store :get-instance ["test"] 100000) ;; => nil (f/send store :add-instance ["test"] {100000 {:timestamp 1000000 :data 23.4} 200000 {:timestamp 2000000 :data 33.4} 300000 {:timestamp 3000000 :data 43.4} 400000 {:timestamp 4000000 :data 53.4} 500000 {:timestamp 5000000 :data 63.4}}) ;; => [1 1 1 1 1]

construir abstracción

por lo tanto, debido a que la función make-store construye un objeto de store que es completamente independiente, se pueden definir funciones para aprovechar esto.

(ns redis-ex.app (:require [hara.fn :as f])) (defn get-last-3-elements [st kdir] (f/send st :get-last kdir 3))

Y si quieres usarlo ... harías algo como:

(ns redis-ex.test-app0 (:use redis-ex.app redis-ex.history) (:require [taoensso.carmine :as car])) (def store (hist/make-store (car/make-conn-pool) (car/make-conn-spec) "test")) (get-last-3-elements ["test"] store) ;;=> [{:timestamp 3000000 :data 43.4} {:timestamp 4000000 :data 53.4} {:timestamp 5000000 :data 63.4}]

burlándose con clojure - estilo ''OO''

Entonces, la verdadera ventaja de esto es que el método de get-last-3-elements puede estar en un espacio de nombres completamente diferente. no depende en absoluto de la implementación de la base de datos y, por lo tanto, probar esta función ahora solo requiere un arnés ligero.

Los simulacros son entonces triviales de definir. La prueba del espacio de nombres redis-ex.usecase se puede realizar sin cargar en ninguna biblioteca de base de datos.

(ns redis-ex.test-app1 (:use redis-ex.app)) (defn make-mock-store [] {:database [{:timestamp 5000000 :data 63.4} {:timestamp 4000000 :data 53.4} {:timestamp 3000000 :data 43.4} {:timestamp 2000000 :data 33.4} {:timestamp 1000000 :data 23.4}] :get-last (fn [store kdir number] (->> (:database store) (take number) reverse))}) (def mock-store (make-mock-store)) (get-last-3-elements ["test"] mock-store) ;; => [{:timestamp 3000000 :data 43.4} {:timestamp 4000000 :data 53.4} {:timestamp 5000000 :data 63.4}]


Es un post viejo pero quería responderle.

No clojure no tiene soporte OO, y no tiene soporte CLOS. El sistema de objetos subyacente del entorno solo está disponible en el sentido de la interoperabilidad, no para hacer sus propias jerarquías de clase / objetos en el cierre. Clojure está hecho para facilitar el acceso a las bibliotecas CLR o JVM, pero la compatibilidad con OOP termina aquí.

Clojure es un cierre lisp y soporte y macros. Con estas 2 características en mente, puede desarrollar un sistema de objetos básico en unas pocas líneas de código.

Ahora el punto es, ¿realmente necesitas OOP en un dialecto claro? Yo diría que no y sí. No, porque la mayoría de los problemas se pueden resolver sin un sistema de objetos y de manera más elegante en cualquier contexto. Yo diría que sí, porque todavía necesitarás POO de vez en cuando y es mejor proporcionar una implementación de referencia estándar que tener a cada geek implementándolo.

Le recomiendo que eche un vistazo al libro On Lisp , de Paul Graham. Puede consultarlo gratuitamente en línea.

Este es realmente un buen libro, que realmente capta la esencia de lisp. Tendrá que adaptar un poco la sintaxis para no pensar, pero los conceptos siguen siendo los mismos. Importante para su pregunta, uno de los últimos capítulos muestra cómo definir su propio sistema de objetos en lisp.

Un comentario lateral, clojure abrazo inmutabilidad. Puede crear un sistema de objetos mutables en el juego, pero si se adhiere a la inmutabilidad, su diseño, incluso utilizando OOP, será bastante diferente. La mayoría de los patrones de diseño y construcción estándar se hacen teniendo en cuenta la mutabilidad


Las publicaciones anteriores abordan la pregunta como una pregunta sobre el valor y las posibilidades de implementar un soporte específico para varias características de la programación orientada a objetos en Clojure. Sin embargo, hay una familia de propiedades que están asociadas con ese término. No todos los lenguajes orientados a objetos son compatibles con todos ellos. Y Clojure admite directamente algunas de estas propiedades, ya sea que desee llamar a ese soporte "orientado a objetos" o no. Mencionaré un par de estas propiedades.

Clojure puede admitir el multimethods utilizando su sistema de método múltiple. Las funciones básicas son defmulti y defmethod . (Tal vez no estuvieran disponibles cuando se contestó la pregunta por primera vez).

Una de las características relativamente inusuales de CLOS es su soporte para funciones que se distribuyen en los tipos de argumentos múltiples. Clojure emula ese comportamiento de forma muy natural, como lo sugiere un ejemplo here . (El ejemplo no usa tipos per se, pero eso es parte de la flexibilidad de los métodos múltiples de Clojure. Compare con el primer ejemplo defmethod ).


¿Ha considerado los tipos de datos de Clojure (especialmente el defrecord ), los protocols y los defrecord ? Los tres siempre serán más idiomáticos dentro de Clojure que un puerto de CLOS sobre estos mecanismos.


CljOS es una biblioteca OOP de juguete para Clojure. No es en absoluto sentido de la palabra completa. Solo algo que hice para divertirme.