CLOS для Clojure?

Существует ли что-нибудь вроде CLOS (объектная система Common Lisp) для Clojure?


person Eric Grindt    schedule 28.10.2010    source источник


Ответы (7)


Рассматривали ли вы типы данных Clojure (особенно defrecord), протоколы и мультиметоды? Все три всегда будут более идиоматичны в Clojure, чем порт CLOS поверх этих механизмов.

person cemerick    schedule 28.10.2010
comment
Опять же, если бы мы все слушали, когда кто-то сказал не делать этого, это глупо, Стив Рассел мог бы послушать Джона Маккарти и никогда не компилировать его eval вручную, и у нас, возможно, вообще никогда не было бы Лиспа на компьютере. Так началась долгая история хакеров Лиспа, которые не уважали то, что кто-то другой считал правильным! Поэтому я говорю: дерзайте, портируйте CLOS на Clojure и смотрите, что получится. Действительно, что самое худшее может случиться? :-) - person Ken; 29.10.2010
comment
Я не говорю, не переустанавливайте CLOS. Я просто говорю, что возможности, предоставляемые и поддерживаемые ядром языка, должны, по крайней мере, рассматриваться для данного использования, прежде чем пытаться обращаться с лошадью как с коровой, так сказать. Теперь, если кто-то смелый (или глупый?), все виды объектных систем могут быть построены поверх этих основных средств: определенно CLOS, если кто-то был так мотивирован, а также, вероятно, гораздо более продвинутые пути. - person cemerick; 29.10.2010
comment
Типы данных @cemerick по умолчанию не имеют наследования. isa+multimethods ближе всего, но вам нужно наследовать поля самостоятельно. - person desudesudesu; 03.01.2014
comment
Механизм отправки CLOS звучит очень сложно и немного излишне. Протоколы Clojure не заходят так далеко, а мультиметоды, похоже, не имеют доступа ко всей необходимой информации. - person David Tonhofer; 20.08.2019

В Clojure нет объектной системы по двум причинам:

  1. Clojure специально разработан для размещения на объектно-ориентированной платформе, а затем просто поглощает объектную систему базовой платформы. т.е. ClojureJVM имеет объектную систему JVM, ClojureCLR имеет объектную систему CLI, ClojureScript имеет объектную систему ECMAScript и так далее.
  2. Рич Хики ненавидит предметы.

Но вы, очевидно, можете реализовать объектную систему в Clojure. В конце концов, Clojure является полным по Тьюрингу.

Микель Эвинс работает над новым подходом к объектно-ориентированному программированию, который он называет Категории. У него есть реализации для нескольких Лиспов, включая Clojure (хотя не все порты гарантированно всегда актуальны).

Категории постепенно поглощаются Bard, новым диалектом Лиспа, который разрабатывает Микель, со встроенными категориями. , в свою очередь, может стать языком реализации для Closos, идея Микеля имел для того, как разработать операционную систему.)

person Jörg W Mittag    schedule 28.10.2010
comment
Рич Хики ненавидит предметы. Насколько я понимаю, одна из прелестей семейства Лиспов заключается в том, что программист может расширить язык во что угодно, даже если первоначальный автор этого не сделал (или не одобрил :-)). Я снимаю шляпу перед Ричем за отличный языковой дизайн, но держу пари, он согласится с тем, что предоставление пользователю возможности добавлять полную объектную систему является одной из сильных сторон Clojure (или любого Лиспа). - person Ralph; 15.05.2011

Clojure не имеет CLOS и не хочет CLOS, но вы можете реализовать его.

Clojure хочет быть неизменяемым, поэтому иметь изменяемый объектно-ориентированный подход было бы довольно глупо, но вы можете иметь своего рода объектно-ориентированный подход.

С этими тремя вещами вы сможете удовлетворить все свои потребности, но в большинстве случаев лучше просто использовать обычные функции и стандартные структуры данных.

person nickik    schedule 28.10.2010
comment
Я считаю, что каждый язык должен иметь режим для начинающих и режим для экспертов. В начальном режиме опасные функции, такие как OO, могут быть исключены, но эксперту должно быть разрешено использовать эти функции в ограниченных обстоятельствах (определяется экспертом). Например, Java не поддерживает макросы в стиле C, потому что Джеймс Гослинг и другие считают их опасными. Хотя есть обходные пути, эксперты иногда ругают их отсутствие. - person Ralph; 15.05.2011

Использование парадигмы объектно-ориентированного программирования идеально подходит для написания слабосвязанного кода, имитации и тестирования. 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}]
person zcaudate    schedule 30.11.2012
comment
Именно так. Вот как это выглядит сделано по схеме. Конструктор объекта — это функция, которая возвращает функцию диспетчеризации метода, обозначающую объект. Отправка сообщения объекту вызывает эту функцию отправки с соответствующим именем метода и параметрами. Затем вы можете получить новую функцию отправки для замены старого объекта, если внутреннее состояние объекта было изменено вызовом метода.... - person David Tonhofer; 20.08.2019
comment
... Методы, удерживаемые функцией отправки, которые являются замыканиями, имеющими доступ к контексту, действительному во время создания функции отправки, что соответствует полям объекта в Java, только недоступным, поскольку нет прямого доступа к этому контексту. В Clojure функция отправки может быть заменена картой (которую можно использовать как функцию, но она выглядит лучше и ее можно легко исправить). Небольшой пример здесь на основе кода из Joy of Clojure. - person David Tonhofer; 20.08.2019
comment
Ага. точно. этот пример великолепен. - person zcaudate; 20.08.2019

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

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

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

person Mars    schedule 02.11.2013

CljOS — это игрушечная ООП-библиотека для Clojure. Это ни в коем случае не полное. Просто то, что я сделал, чтобы повеселиться.

person divs1210    schedule 19.10.2014

Это старый пост, но я хотел ответить на него.

Никакой clojure не поддерживает OO и не поддерживает CLOS. Базовая объектная система среды едва доступна с точки зрения взаимодействия, а не для создания собственных иерархий классов/объектов в clojure. Clojure создан для простого доступа к библиотекам CLR или JVM, но на этом поддержка ООП заканчивается.

Clojure — это lisp, поддерживающий замыкания и макросы. Имея в виду эти две функции, вы можете разработать базовую объектную систему в несколько строк кода.

Теперь дело в том, действительно ли вам нужно ООП на диалекте лиспа? Я бы сказал нет и да. Нет, потому что большинство проблем можно решить без объектной системы и более элегантно на любом лиспе. Я бы сказал да, потому что время от времени вам все равно понадобится ООП, и тогда лучше предоставить стандартную эталонную реализацию, чем каждый гик реализует свою собственную.

Я бы порекомендовал вам взглянуть на книгу On Lisp Пола Грэма. Вы можете бесплатно проконсультироваться с ним онлайн.

Это действительно хорошая книга, которая действительно схватывает суть lisp. Вам придется немного адаптировать синтаксис для clojure, но концепции останутся прежними. Важно для вашего вопроса, одна из последних глав показывает, как определить вашу собственную объектную систему в lisp.

Дополнительное замечание: clojure поддерживает неизменность. Вы можете создать изменяемую объектную систему в clojure, но если вы придерживаетесь неизменности, ваш дизайн даже с использованием ООП будет совсем другим. Большинство стандартных шаблонов проектирования и конструкции созданы с учетом изменчивости.

person Nicolas Bousquet    schedule 21.07.2011