Специальных шаблонов для DSL не существует - вы просто берете инструменты, доступные на языке , и стараетесь сделать его максимально удобным и близким к домену . Лисп просто дает вам больше инструментов, чем другие языки.
Для конкретного примера хорошего DSL посмотрите ClojureQL . Изначально SQL создавался как DSL для реляционных баз данных. И это очень удобно для работы с консоли ... но не с языка программирования, такого как Java или Clojure. Java поставляется с большими платформами ORM, такими как Hibernate, а Clojure предлагает простой DSL, который так же удобен, как и оригинальный SQL, но полностью работает как часть языка:
(select (table :users) (where (= :id 5)))
Обычно в Lisp DSL используются такие конструкции, как defsomething
. Например, в одной книге (извините, я не помню ее названия) есть пример сопоставления с образцом в тексте. Автор создает модуль с рядом соответствий, таких как ?
для одного слова, +
для одного или нескольких слов, *
для нуля или более слов и так далее. Для этого он создает макрос defmatcher
, который принимает некоторый синтаксис и добавляет обработчик для этого синтаксиса в центральный реестр. Это просто абстракция - вместо нескольких повторяющихся операций он вводит один макрос, рассказывающий о том, что он на самом деле хочет сделать - определить matcher. Также в этом примере используются как макросы, так и функции высокого порядка.
Итак, еще раз, в DSL на основе Lisp нет ничего особенного - вы просто описываете область домена с помощью инструментов на вашем языке , будь то Java, Clojure или что-то еще. Просто привыкните к языковым возможностям, и вы увидите, как это должно выглядеть.
UPD. Некоторые "реальные" примеры, когда DSL на основе Lisp более удобны, чем, скажем, ООП:
Домен: автосалон
(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 примера) делают код более лаконичным и наглядным.