Существует ли что-нибудь вроде CLOS (объектная система Common Lisp) для Clojure?
CLOS для Clojure?
Ответы (7)
Рассматривали ли вы типы данных Clojure (особенно defrecord
), протоколы и мультиметоды? Все три всегда будут более идиоматичны в Clojure, чем порт CLOS поверх этих механизмов.
eval
вручную, и у нас, возможно, вообще никогда не было бы Лиспа на компьютере. Так началась долгая история хакеров Лиспа, которые не уважали то, что кто-то другой считал правильным! Поэтому я говорю: дерзайте, портируйте CLOS на Clojure и смотрите, что получится. Действительно, что самое худшее может случиться? :-)
- person Ken; 29.10.2010
В Clojure нет объектной системы по двум причинам:
- Clojure специально разработан для размещения на объектно-ориентированной платформе, а затем просто поглощает объектную систему базовой платформы. т.е. ClojureJVM имеет объектную систему JVM, ClojureCLR имеет объектную систему CLI, ClojureScript имеет объектную систему ECMAScript и так далее.
- Рич Хики ненавидит предметы.
Но вы, очевидно, можете реализовать объектную систему в Clojure. В конце концов, Clojure является полным по Тьюрингу.
Микель Эвинс работает над новым подходом к объектно-ориентированному программированию, который он называет Категории. У него есть реализации для нескольких Лиспов, включая Clojure (хотя не все порты гарантированно всегда актуальны).
Категории постепенно поглощаются Bard, новым диалектом Лиспа, который разрабатывает Микель, со встроенными категориями. , в свою очередь, может стать языком реализации для Closos, идея Микеля имел для того, как разработать операционную систему.)
Clojure не имеет CLOS и не хочет CLOS, но вы можете реализовать его.
Clojure хочет быть неизменяемым, поэтому иметь изменяемый объектно-ориентированный подход было бы довольно глупо, но вы можете иметь своего рода объектно-ориентированный подход.
- http://clojure.org/datatypes (посмотрите на defrecord --> лучший из классов и хэш-карт)
- http://clojure.org/protocols (похоже на интерфейсы, но лучше)
- http://clojure.org/multimethods (эффективно, потому что вы можете писать свои собственные функции отправки)
С этими тремя вещами вы сможете удовлетворить все свои потребности, но в большинстве случаев лучше просто использовать обычные функции и стандартные структуры данных.
Использование парадигмы объектно-ориентированного программирования идеально подходит для написания слабосвязанного кода, имитации и тестирования. Clojure делает это так просто.
Одна проблема, с которой я сталкивался в прошлом, заключалась в том, что код зависел от другого кода. Пространства имен Clojure на самом деле усугубляют проблему, если их неправильно использовать. В идеале пространства имен могут быть смоделированы, но, как я обнаружил... с имитациями пространств имен возникает много проблем:
https://groups.google.com/forum/?fromgroups=#!topic/clojure/q3PazKoRlKU
Как только вы начинаете создавать все более и более крупные приложения, пространства имен начинают зависеть друг от друга, и становится действительно неудобно тестировать компоненты более высокого уровня по отдельности, не имея кучу зависимостей. Большинство решений включают перепривязку функций и другую черную магию, но проблема в том, что приходит время тестирования, исходные зависимости все еще загружаются -> что превращается в большую проблему, если у вас есть большое приложение.
Я был мотивирован искать альтернативы после использования библиотек баз данных. Библиотеки баз данных принесли мне столько боли - они загружаются целую вечность и обычно являются ядром вашего приложения. Очень сложно протестировать ваше приложение без добавления всей базы данных, библиотеки и связанных с ними периферийных устройств в ваш тестовый код.
Вы хотите иметь возможность упаковывать свои файлы, чтобы части вашей системы, которые зависят от кода вашей базы данных, могли быть «заменены». Методология объектно-ориентированного проектирования дает ответ.
Извините, что ответ довольно длинный... Я хотел дать хорошее обоснование того, почему объектно-ориентированный дизайн используется чаще, чем как он используется. Поэтому пришлось использовать реальный пример. Я попытался сохранить объявления ns
, чтобы структура примера приложения была максимально ясной.
существующий код стиля clojure
В этом примере используется carmine
, клиент Redis. С ним относительно легко работать, и он быстро запускается по сравнению с korma и datomic, но библиотека базы данных по-прежнему остается библиотекой базы данных:
(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})
существующий тестовый код
все функции должны быть протестированы... в этом нет ничего нового, это стандартный код 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]
объектно-ориентированный механизм диспетчеризации
Мысль о том, что «ОО» не зло, а на самом деле очень полезно, пришла ко мне после просмотра этого выступления Миско Хевери:
http://www.youtube.com/watch?v=XcT4yYu_TTs
Основная идея заключается в том, что если вы хотите создать большое приложение, вы должны отделить «функциональность» (внутреннюю часть программы) от «проводки» (интерфейсов и зависимостей). Чем меньше зависимостей, тем лучше.
Я использую хеш-карты clojure в качестве «объектов», потому что они не имеют зависимостей от библиотек и являются полностью универсальными (см. Брайан Марик, говорящий об использовании той же парадигмы в ruby — http://vimeo.com/34522837).
Чтобы сделать ваш код clojure «объектно-ориентированным», вам нужна следующая функция — (send
украдена из smalltalk), которая просто отправляет функцию, связанную с ключом в карте, если она связана с существующим ключом.
(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))
Я предоставляю реализацию в служебной библиотеке общего назначения (https://github.com/zcaudate/hara в пространстве имен hara.fn
). Это 4 строки кода, если вы хотите реализовать это для себя.
определение объекта "конструктор"
теперь вы можете изменить исходную функцию make-store
, чтобы добавить функции на карту. Теперь у вас есть уровень косвенности.
;;; 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]
построить абстракцию
поэтому, поскольку функция make-store
создает объект store
, который полностью автономен, можно определить функции, чтобы воспользоваться этим преимуществом.
(ns redis-ex.app
(:require [hara.fn :as f]))
(defn get-last-3-elements [st kdir]
(f/send st :get-last kdir 3))
и если вы хотите его использовать... вы должны сделать что-то вроде:
(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}]
издевательство над clojure — стиль «ОО»
Таким образом, настоящее преимущество в том, что метод get-last-3-elements
может находиться в совершенно другом пространстве имен. это вообще не зависит от реализации базы данных, поэтому для тестирования этой функции теперь требуется только облегченная обвязка.
mocks тогда тривиально определить. Протестировать пространство имен redis-ex.usecase можно без загрузки каких-либо библиотек базы данных.
(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}]
В более ранних сообщениях этот вопрос рассматривался как вопрос о ценности и возможностях реализации конкретной поддержки различных функций объектно-ориентированного программирования в Clojure. Однако существует ряд свойств, связанных с этим термином. Не все объектно-ориентированные языки поддерживают их все. И Clojure напрямую поддерживает некоторые из этих свойств, независимо от того, хотите ли вы называть эту поддержку «объектно-ориентированной» или нет. Я упомяну пару таких свойств.
Clojure может поддерживать диспетчеризацию иерархически определенных типов, используя свою мультиметодную систему. Основные функции: defmulti и defmethod. (Возможно, они не были доступны, когда на вопрос был получен первый ответ.)
Одной из относительно необычных особенностей CLOS является поддержка функций, которые диспетчеризируют типы с несколькими аргументами. Clojure очень естественно эмулирует такое поведение, как показано в примере здесь. (В примере не используются типы как таковые, но это часть гибкости мультиметодов Clojure. Сравните с первым примером здесь.)
CljOS — это игрушечная ООП-библиотека для Clojure. Это ни в коем случае не полное. Просто то, что я сделал, чтобы повеселиться.
Это старый пост, но я хотел ответить на него.
Никакой clojure не поддерживает OO и не поддерживает CLOS. Базовая объектная система среды едва доступна с точки зрения взаимодействия, а не для создания собственных иерархий классов/объектов в clojure. Clojure создан для простого доступа к библиотекам CLR или JVM, но на этом поддержка ООП заканчивается.
Clojure — это lisp, поддерживающий замыкания и макросы. Имея в виду эти две функции, вы можете разработать базовую объектную систему в несколько строк кода.
Теперь дело в том, действительно ли вам нужно ООП на диалекте лиспа? Я бы сказал нет и да. Нет, потому что большинство проблем можно решить без объектной системы и более элегантно на любом лиспе. Я бы сказал да, потому что время от времени вам все равно понадобится ООП, и тогда лучше предоставить стандартную эталонную реализацию, чем каждый гик реализует свою собственную.
Я бы порекомендовал вам взглянуть на книгу On Lisp Пола Грэма. Вы можете бесплатно проконсультироваться с ним онлайн.
Это действительно хорошая книга, которая действительно схватывает суть lisp. Вам придется немного адаптировать синтаксис для clojure, но концепции останутся прежними. Важно для вашего вопроса, одна из последних глав показывает, как определить вашу собственную объектную систему в lisp.
Дополнительное замечание: clojure поддерживает неизменность. Вы можете создать изменяемую объектную систему в clojure, но если вы придерживаетесь неизменности, ваш дизайн даже с использованием ООП будет совсем другим. Большинство стандартных шаблонов проектирования и конструкции созданы с учетом изменчивости.