Подтвердить что ты не робот

CLOS для Clojure?

Существует ли что-то вроде CLOS (Common Lisp Object System) для Clojure?

4b9b3361

Ответ 1

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

Ответ 2

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

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

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

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

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

Ответ 3

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

Clojure хочет быть неизменным, чтобы изменчивое OO было своего рода глупостью, но вы можете иметь своего рода OO.

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

Ответ 4

Использование парадигмы OO идеально подходит для написания слабосвязанного кода, насмешки и тестирования. Clojure делает это так легко.

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

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

Как только вы начинаете создавать все больше и больше приложений, пространства имен запускаются в зависимости друг от друга, и они действительно бесполезны, чтобы протестировать компоненты более высокого уровня отдельно, не имея кучу зависимостей. Большинство решений связаны с повторной привязкой функций и другой черной магией, но проблема заключается в том, что пришло время тестирования, исходные зависимости все еще загружаются → что становится большой проблемой, если у вас большое приложение.


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

Вы хотите иметь возможность упаковывать ваши файлы, чтобы части вашей системы, зависящие от вашего кода базы данных, могли быть "выгружены". Ответ на него дает методология OO.

Мне жаль, что ответ довольно длинный... Я хотел дать хорошее обоснование , почему дизайн OO используется больше, чем , как. Поэтому нужно было использовать фактический пример. Я попытался сохранить декларации ns, чтобы структура приложения-примера была как можно более ясна.

существующий Clojure код стиля

В этом примере используется carmine, который является клиентом redis. Относительно легко работать и быстро запускается по сравнению с кормой и 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)) [email protected])))

(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 code

(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]

ориентированный на объект механизм отправки

Идея о том, что "OO" не зла, но на самом деле весьма полезная, пришла ко мне после просмотра этого разговора Мишко Хевери:

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

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

Я использую хэш-карты Clojure как "объекты", потому что они не имеют библиотечных зависимостей и являются полностью родовыми (см. Брайан Марик, говорящий об использовании той же парадигмы в рубине - 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 - стиль "OO"

Таким образом, реальное преимущество этого заключается в том, что метод 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}]

Ответ 5

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

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

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

Ответ 6

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

Ответ 7

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

Нет clojure не поддерживает OO и не поддерживает CLOS. Основополагающая объектная система среды едва доступна в наличии возможности взаимодействия, а не для создания собственных иерархий классов/объектов в clojure. clojure создан для легкого доступа к библиотекам CLR или JVM, но поддержка OOP завершается здесь.

Clojure является lisp и поддерживает замыкания и макросы. Имея в виду тезисы 2, вы можете разработать базовую систему объектов в нескольких строках кода.

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

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

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

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