CLOS для Clojure? - PullRequest
       31

CLOS для Clojure?

22 голосов
/ 28 октября 2010

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

Ответы [ 7 ]

18 голосов
/ 28 октября 2010

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

17 голосов
/ 29 октября 2010

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

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

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

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

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

11 голосов
/ 29 октября 2010

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

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

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

4 голосов
/ 30 ноября 2012

Использование парадигмы ОО идеально для написания слабосвязанного кода, макетов и тестирования.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 - стиль '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}]
3 голосов
/ 02 ноября 2013

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

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

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

0 голосов
/ 19 октября 2014

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

0 голосов
/ 21 июля 2011

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

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

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

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

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

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

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

...