DSL в Clojure, который заменяет объектно-ориентированное программное решение?

#java #clojure #domain-driven-design #data-modeling

#java #clojure #дизайн, управляемый доменом #моделирование данных

Вопрос:

Мне было интересно, знает ли кто-нибудь конкретный пример DSL в Clojure, который заменяет абстракцию и читаемость хорошей OO-программы (написанной, скажем, на Java).

Я пытался использовать модель данных OO (основанную на «bean», с очень абстрактными методами, которые скрывают базовые реализации) в clojure moeity.

Я знаю, что существуют «макросы» и «функции более высокого порядка», однако я никогда не видел, чтобы они применялись к набору данных реального мира, который легко понять (например, система регистрации курсов, или автосалон, или биллинговая система, или что-то в этом роде, вспомните печально известныйПримеры «JPetStore», которые Hibernate и Ibatis популяризировали в последнее десятилетие).

Существует ли какой-либо специфичный для домена шаблон для изучения того, как моделировать системы реального мира в Clojure с протоколами и функциями более высокого порядка?

Ответ №1:

Для DSL нет специальных шаблонов — вы просто берете инструменты, доступные на языке, и пытаетесь сделать его максимально удобным и близким к предметной области. Lisp просто предоставляет вам больше инструментов, чем другие языки.

Для конкретного примера хорошего DSL посмотрите на ClojureQL. Изначально SQL был создан как DSL для реляционных баз данных. И это очень удобно для работы с консоли… но не из языка программирования, такого как Java или Clojure. Java поставляется с большими фреймворками ORM, такими как Hibernate, а Clojure предлагает простой DSL, который так же удобен, как оригинальный SQL, но полностью работает как часть языка:

 (select (table :users) (where (= :id 5)))
  

Обычным явлением в DSL на Lisp является использование таких конструкций, как defsomething . Например, в одной книге (извините, я не помню ее названия) есть пример сопоставления с шаблоном в тексте. Автор создает модуль с несколькими сопоставителями, например ? , для одного слова, для одного или нескольких слов, * для нуля или более слов и так далее. Для этой цели он создает макрос defmatcher , который принимает некоторый синтаксис и добавляет обработчик для этого синтаксиса в центральный реестр. Это просто абстракция — вместо нескольких повторяющихся операций он вводит один макрос, сообщающий, что он на самом деле хочет сделать — определить сопоставитель. Также в этом примере используются как макросы, так и функции высокого порядка.

Итак, еще раз, в DSL на основе Lisp нет ничего особенного — вы просто описываете доменную область с помощью инструментов, которые у вас есть на вашем языке, будь то Java, Clojure или что-то еще. Просто привыкните к языковым возможностям, и вы увидите, как это должно выглядеть.

UPD.Некоторые «реальные» примеры, когда DSL на основе Lisp удобнее, чем, скажем, ООП:

Домен: car dillership

 (defcar my-cool-car :wheels 4, :doors 2, :color red) ;; in Java you need Factory
(def car1 (make-car my-cool-car))                    ;; and lots of methods to 
                                                     ;; add features to cars and 
                                                     ;; cars to factory
  

Домен: биллинговая система

 (transaction                ;; in Java you cannot create wrapping constructs
  (withdraw account1 100)   ;; so you have to use inheritance, annotations, etc.
  (put account2 100))       ;; which is much more code
  

Домен: некоторая веб-служба, которая обрабатывает запросы нескольких типов

 (defhandler :show-all (fn [params] ...))     ;; adds defined function to the
(defhandler :find-best (fn [params] ...))    ;; map of :message-type -> function
...
(defn handle [message]
   (let [msg-type (:type message), msg-params (:params message)]
     (if (contains? *handlers* msg-type) 
       ((*handlers* msg-type) msg-params)
       (throw (Exception. (concat "No handler for type" (:type message)))))))
  

В этих примерах нет ничего особенного — вы можете реализовать их все на Java или любом другом языке. Однако такие вещи, как ключевые слова (1-й пример), функции более высокого порядка (2-й пример), макросы (все 3 примера), делают ваш код более кратким и описательным.

Ответ №2:

Я не знаю, это ли то, что вы ищете, но я прочитал книгу о Clojure (Программирование Clojure; Прагматичные программисты), в которой содержится пример хорошего маленького DSL. Вы можете найти код по адресу https://github.com/stuarthalloway/lancet . По сути, lancet — это что-то вроде make или ant, но реализовано как Clojure-DSL.

Комментарии:

1. … lancet — это DSL, но на самом деле это не «реальная» проблема, поскольку большинство программистов никогда не пытались написать такую систему: ознакомьтесь с добрым старым приложением Google: jpetstore….

Ответ №3:

ООП хорош в том смысле, что мы действительно к нему привыкли, и он соответствует концепциям в реальном мире.

Вы можете увидеть, как построить систему ООП с maps и несколькими методами (что-то похожее на прототипное наследование в JS) в The Joy of Clojure, глава 9.

В книге Пола Грэма по lisp показано, как создать объектную систему на lisp. Должно быть легко адаптировать его к Clojure.

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

Кроме того, если вы видите, что основная концепция во многих приложениях (например, Java pet store) является процедурной / функциональной; они не используют такие понятия объектов, как обобщение или инкапсуляция.

Компонент не имеет поведения. Данные без инкапсуляции имеют геттеры / сеттеры, которые разрешают общедоступный доступ к ним. Для того, что это дает вам, вы могли бы использовать структуру C (на типизированном языке) или карту на динамическом языке, таком как Clojure.

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

Это не так сложно.

Назовите свои компоненты, как в JAVA… Но реализуйте их как карты Clojure или как записи

Назовите свои сервисы и реализуйте их как функции.

Нужны фабрики? Создайте функции, которые принимают конфигурацию и возвращают функции.

Требуется внедрение зависимостей? Ну, вы можете использовать параметры функции.