пятница, 20 августа 2010 г.

Common Lisp и DSL. Другой взгляд.

swizard опубликовал статью о Common Lisp и DSL, в которой достаточно ясно изложил мнение, что наиболее эффективное использование Common Lisp заключается в разработке DSL. Мало того, он предлагает начинающим начинать именно с этого. Позвольте не согласится.

Кратко - DSL не нужны кроме тех редких случаев, когда они очень хороши и реально упрощают решение задачи. Это не имеет отношения к Common Lisp, либо к какому либо другому языку. Например, Эрик Раймонд писал о DSL как о классической традиции программирования под UNIX и всяческих расхваливал преимущества данного подхода. Однако на практике лишь малая часть Unix-программ используют какие-либо DSL. Это однозначно свидетельствует о том, что разработчики, несмотря на все разрекламированные свойства DSL, стараются избегать их использования (да, я действительно считаю, что "миллионы мух не могут ошибаться", поскольку речь идёт не о мухах, а о, по большей части, талантливых разработчиках).

Если говорить о Common Lisp, то мне совершенно не понятно на чём основано мнение, что "common lisp и заслужил себе славу мета-языка " (с). В реальной практике программирования на Common Lisp использование DSL встречается редко. Связано это с тем, что, на мой взгляд, использование DSL просто противоречит духу Common Lisp, по крайней мере, в его современном виде.

Наиболее сильной и характерной чертой Common Lisp является мощная поддержка интерактивной разработки, что значительно упрощает постоянный рефакторинг кода и проектирование "снизу вверх", которые характерны для современных подходов к разработке. Программирования на основе DSL, однако, требует другого подхода: задача делится на две подзадачи - проектирование DSL и описание решения на этом DSL. Проектирования языка программирования, пусть даже это маленький DSL, это сложная задача, повышающая требования к качеству проектирования системы. В контексте современных задач, когда приложение работает сразу во многих областях, также существенно усложняется выделения предметной области для конкретного DSL. Сложность возникающих проблем дизайна приводит к необходимости проектирования "сверху вниз", что стразу убивает большую часть преимуществ интерактивной разработки. Поскольку для современных задач характерна неточность в требованиях, которые выясняются по мере разработки (обычно, ближе к концу), то проектирование "сверху вниз" связано со слишком большими рисками и сопутствующими накладными расходами.

Использование DSL возможно и целесообразно лишь в некоторых областях, но не для широкого круга задач. ИМХО, мнение о том, что при программировании на Common Lisp следует широко использовать DSL основано на позиции старой "lisp-гвардии", представители которой застряли где-то в начале 80-ых.

26 комментариев:

  1. Надо заметить, что грань между Embedded DSL и хорошим API довольно тонка, и тем она тоньше, чем язык экспрессивнее, поэтому я скорее соглашусь со swizard'ом.

    > Однако на практике лишь малая часть Unix-программ используют какие-либо DSL.

    Интерпретаторы shell — яркий пример DSL, далее регулярные выражения, конфигурация iptables и т.п.

    > Связано это с тем, что, на мой взгляд, использование DSL просто противоречит духу Common Lisp, по крайней мере, в его современном виде.

    Вот на это, хотелось бы услышать более развёрнутый ответ.

    ОтветитьУдалить
  2. @bsdemon

    Нет, граница между API и eDSL достаточно чёткая, ибо DSL предполагает стадию стадию трансляции кода.

    > Интерпретаторы shell — яркий пример DSL

    Нет, ибо тот же bash и т.п. это достаточно полноценные языки программирования, без чётко выделенной предметной области.

    > Вот на это, хотелось бы услышать более развёрнутый ответ.

    Там только углублять тезисы. Чем отличаются различные подходы к проектированию. Почему я предпочитаю "снизу вверх" и т.д.

    ОтветитьУдалить
  3. Я тоже со swizard'ом не согласился по философии статьи. Он во введении говорит, что научный метод познания (использование старого опыта там, где возможно) - на голову ошибочное.

    Лисп - он вообще хорош тем, что начинать изучать его можно с любой стороны.

    ОтветитьУдалить
  4. > Нет, граница между API и eDSL достаточно чёткая, ибо DSL предполагает стадию стадию трансляции кода.

    Ну если API представляется макросом, то этот макрос как раз и есть - стадия трансляции кода (макрос делает преобразование sexp -> sexp (aka AST)).

    Если брать примеры каких-нибудь простых макросов:

    1) макросы with-* имеют в качестве domain цели автоматическое конструирование/де-конструирование объектов.

    2) анафорические макросы имеют в качестве domain автоматическое связывание переменных в окружении.

    3) ещё - описание системы в ASDF это DSL или нет?

    4) Макросы подобные defsystem в ASDF, которые написаны в соответствии с некоторыми правилами (синтаксис этого DSL) и определяют (генерируют, или транслируют), скажем, классы и методы. Это DSL?

    ОтветитьУдалить
  5. @quasimoto

    > Ну если API представляется макросом, то этот
    > макрос как раз и есть - стадия трансляции кода

    Нет, макрос не определяет нового языка. Макросы сами по себе не определяют DSL и никакого отношения к DSL не имеют. Макросы и DSL ортогональны.

    > описание системы в ASDF это DSL или нет?

    Нет. ASDF это всего лишь формат данных.

    ОтветитьУдалить
  6. Весь смысл использования DSL в CL в том, что DSL не является инородной конструкцией языка, а вплетён в язык с помощью макросов. Таким образом, получается очень легко использовать CL из DSL и наоборот. На мой взгляд, в этом суть использования метапрограммрования и DSL в лиспе. А проектировать DSL "сверху вниз" вроде никто и не предлагает. Всегда лучше обойтись стандартными конструкциями лиспа, и использовать DSL для того, для чего он действительно нужен.

    ОтветитьУдалить
  7. @Valeriy Fedotov

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

    ОтветитьУдалить
  8. > Макросы и DSL ортогональны.

    Т.е. когда я ввожу себе DSL с помощью двух-трёх макросов, то я... не ввожу себе DSL?) Мне всё-таки кажется что если я придумал себе DSL и реализовал его "на макросах" то иначе как "использованием макросов для реализации DSL" это не назвать. Следовательно макросы это (в том числе) средство построения DSL-ей. Кстати, в той статье утверждается что конструкции вроде define-vop в SBCL это DSL, ну так это DSL на макросах.

    > ASDF это всего лишь формат данных.

    Ну так всё формат данных :) (код - тоже данные, скажем). Очень общая характеристика "формат данных".

    Я бы сказал так:

    1) Если принять типичное определение DSL (как в википедии, например), то некий декларативный формат данных с определённым синтаксисом, который однозначно определяет какие-то вычислители - это DSL. В этом смысле любой более-менее сложный макрос вводит DSL по отношению к своим входным SEXP-ам (и в этом смысле язык defsystem это DSL и define-vop - тоже).

    2) Акцентировать внимание на L (Language) и требовать полноту этого языка - неправильно. Требование полноты как раз определяет common domain language (CDL), а DSL это как раз декларативный (обычно) язык который : -*- имеет определённый синтаксис (набор синтаксических правил), -*- и имеет набор правил трансляции DSL -> CDL.

    ОтветитьУдалить
  9. > Т.е. когда я ввожу себе DSL

    Понятия макросов и DSL ортогональны. Ты можешь использовать макросы при определении eDSL, а можешь и не использовать. Связи между макросами и DSL нет.

    > конструкции вроде define-vop в SBCL это DSL,

    И?

    > формат данных с определённым синтаксисом,
    > который однозначно определяет какие-то
    > вычислители - это DSL

    mp3 это DSL? И HTML тоже DSL? Для примера - PostScript - это DSL, а PDF, хотя и основан на PostScript, - формат.

    > и требовать полноту этого языка

    Никто её не требует. Понятие Тьюринг-полноты ортогонально понятию DSL.

    Вообще, вынужден отослать к изестному труду Э.Раймонда (Исскустно программирования для Unix). Там теме DSL посвящена одтельная глава. И там есть примеры типичных DSL. В своём понимании что такое DSL я ориентируюсь на них.

    Иначе всё что угодно можно объявить как DSL, а в этом случае весь смысл понятия утрачивается.

    ОтветитьУдалить
  10. @quasimoto

    Кстати, в википедии на редкость тупая статья

    ОтветитьУдалить
  11. > И?

    Ну да, DSL на макросах, ок.

    > Ты можешь использовать макросы при определении eDSL, а можешь и не использовать. Связи между макросами и DSL нет.

    Ну с этим я не спорю - конечно, макросы это средство (и для DSL и для метапрограммирования). Тем не менее вот ты писал:

    > Если говорить о Common Lisp, то мне совершенно не понятно на чём основано мнение, что "common lisp и заслужил себе славу мета-языка " (с).

    Метапрограммирование это в первую очередь кодогенерация с использованием макросов - то как связаны между собой стадии macroexpand/eval/compile - это и есть "слава мета языка". Благодаря этой связи то что в языках без метапрограммирования было бы надстроенным интерпретатором в CL может быть компилятором (и тут вопрос - компилятором чего во что? ответ - DSL в CL примитивы, вот тут как раз и видна связь DSL и метапрограммирования (кодогенерации)).

    > mp3 это DSL? И HTML тоже DSL? Для примера - PostScript - это DSL, а PDF, хотя и основан на PostScript, - формат.

    Я написал что такое DSL в предыдущем посте. mp3 это не DSL так как mp3 это не язык (под L подразумевают формальный язык, у которого есть чёткое определение). DSL это во-первых язык (заданный любой грамматикой) который не полон (поэтому specific, точнее - его полнота вообще нас не интересует (поэтому он декларативен)) и во-вторых правила трансляции этого языка в (другой) полный язык (CDL). Т.е. говорить что язык HTML это DSL имеет смысл только в том случае, если добавить определённые правила трансляции (например отрисовка этого HTML). То же самое касается XML - так это просто декларативный язык, в добавлении определённых правил трансляции это уже DSL того или иного назначения (есть любители строить DSL на XML :) типа "программа управления роботом").

    > Иначе всё что угодно можно объявить как DSL, а в этом случае весь смысл понятия утрачивается.

    Формально, определение такое: DSL = (Lang, Trans (Lang -> CDM)), чем бы ни был Lang.

    ОтветитьУдалить
  12. > Метапрограммирование это в первую очередь
    > кодогенерация с использованием макросов

    Сфига? У каждого языка будет своё определения метапрограммирования? Что будем делать с метапрограммированием в C++ или том же Haskell?

    > Формально, определение такое: DSL =
    > (Lang, Trans (Lang -> CDM)), чем бы ни был Lang.

    Это ещё сфига?

    ОтветитьУдалить
  13. > Сфига? У каждого языка будет своё определения метапрограммирования? Что будем делать с метапрограммированием в C++ или том же Haskell?

    Да, в CL эти "макросы" известны как macros (как и в Nemerle), в Haskell реализуются с помощью TH, в F#, OCaml (camlp) и C++ (templates) они имеют более ограниченные возможности - но принцип везде один и тот же.

    > Это ещё сфига?

    Ну так по науке - такое определение )

    ОтветитьУдалить
  14. > они имеют более ограниченные возможности

    Сфига это ещё?

    > Ну так по науке - такое определение )

    Ты их сам изобретаешь?

    ОтветитьУдалить
  15. >> они имеют более ограниченные возможности
    > Сфига это ещё?

    C++ templates vs. CL macros - ? В первом нет возможности преобразования произвольного кода, например (фактически там главное-то - диспетчеризация по классу).

    > Ты их сам изобретаешь?

    Сфига мне самому их изобретать? ;D Так принято выражаться о языках в теории формальных языков. То есть этой записью мы утверждаем, что

    1) Есть язык Lang, заданный произвольной грамматикой. Чаще всего простой грамматикой - возможно даже что язык конечен.
    2) Есть язык CDL, для которого доказана его Тьюринг-полнота.
    3) Полнота языка Lang нас не интересует, так как:
    4) Есть отображение Lang -> CDL (трансляция).

    В этом случае Lang это DSL. Естесвенно такой формализм очень обтекаем - на практике нужно определять что это за domain, но если это сделано, то любой DSL попадает под такое определение.

    К примеру:

    1) Мы берём язык s-выражений,
    2) И полный язык (вычислитель) CL
    3) определяем над s-выражениями какие-то семантические правила (как в defsystem или define-vop).
    4) определяем макрос который проводит трансляцию:

    (s-выражение, набор наших семантических правил) -> код на CL (например, определение класса и ряда его методов).

    Язык s-выражений (которому изоморфен HTML или XML) это НЕ DSL - ты это наверно имеешь ввиду. Но пара (s-выражение, набор наших семантических правил) это и есть DSL, по определению.

    ОтветитьУдалить
  16. (выражение, набор наших семантических правил)
    ^
    это, кстати, Кнут придумал - вообще применительно к любой трансляции языков. Lang является DSL при неких дополнительных условиях (потому specific).

    ОтветитьУдалить
  17. > C++ templates vs. CL macros - ?

    Давай начнём с того, что язык шаблонов С++ является Тьюринг-полным, на нём даже интепретатор лиспа (времени компиляции) реализовали...

    > Естесвенно такой формализм

    Перестань курить абстракции в таких дозах. DSL это термин из инженерной практики проектирования. Никакие надуманные формализмы здесь не при чём.

    ОтветитьУдалить
  18. > Давай начнём с того, что язык шаблонов С++ является Тьюринг-полным, на нём даже интепретатор лиспа (времени компиляции) реализовали...

    Я уже читал это на ЛОРе :) Я сказал только "нет возможности преобразования произвольного кода" - то факт.

    > DSL это термин из инженерной практики проектирования.

    Ну, может я и правда перегибаю с абстракциями. Для меня это просто способ подвести общую основу под метапрограммированием и DSL в разных языках (вот мы определили - и больше можно не путаться).

    По крайней мере ты согласился, что выражения в define-vop (вместе с правилами их трансляции в CL код) это DSL. Но почему выражения в defsystem (тоже - вместе с правилами генерации кода макросом) - нет?

    ОтветитьУдалить
  19. Я вот думаю что код в defsysem в *.asd это Domain Specific Language domain которого это System Definition Facility (который ещё another) ;)

    ОтветитьУдалить
  20. @quasimoto

    Я недостаточно знаком с внутренним устройством SBCL, что бы судить обоснованно, но вполне допускаю, что define-vop действительно связано с DSL.

    А ASDF не определяет никакого языка, на котором можно программировать. ASDF ни в коем случае не может считаться DSL. При желании, ASDF можно рассматривать как data driven систему, но не более.

    ОтветитьУдалить
  21. > А ASDF не определяет никакого языка, на котором можно программировать.

    Язык это просто произвольное множество наборов значков. Т.е. {a, b, aa, bb} - язык в котором можно сказать a или a или aa или bb, а его словарь - {a, b}.

    Я к тому, что называют "языком". Он может быть декларативным - т.е. ничего особо не по-программируешь, просто можно декларативно что-то определить. Как в ASDF - вот есть такая-то система. Суть в том что описания этого декларативного языка транслируются в выполняемый код (макросом) - этим и осуществляется "программирование" (в ASDF - задание системы).

    ОтветитьУдалить
  22. > Язык это просто произвольное множество наборов значков.

    Опять неуместная абстракция.

    DSL - это такой специальный язык программирования. ASDF языком программирования не является.

    ОтветитьУдалить
  23. Как мне показалось, у вас представление о DSL как о *языке программирования*, который обязательно должен этап интерпретации или трансляции в хост язык. Общепризнанное определение DSL[1] говорит, что это язык программирования или спецификация, которые потом транслируются в хост язык с помощью интерпретатора/компилятора (external DSL) или кодогенерации (internal DSL). Как раз например, язык описания в ASDF и является спецификацией, поэтому его можно считать DSL. Хотя конечно понятие DSL расплывчато, и даже фаулер пишет об этом в своей книге[2], что разница между API и DSL не так уж ясна, однако, чтобы не подразумевалось под этим термином, главное, чтобы это помогало решить задачу. В этом я с ним полностью согласен.

    [1] > A domain-specific language (DSL) is a programming language or executable specification language that offers, through appropriate notations and abstractions, expressive power focused on, and usually restricted to, a particular problem domain. ( http://homepages.cwi.nl/~arie/papers/dslbib/ )


    > ... a domain-specific language (DSL) is a programming language or specification language dedicated to a particular problem domain, a particular problem representation technique, and/or a particular solution technique. ( http://en.wikipedia.org/wiki/Domain-specific_language )


    > DSLs can be implemented either by interpretation or code generation. Interpretation (reading in the DSL script and executing it at run time) is usually easiest, but code-generation is sometimes essential. ( http://martinfowler.com/bliki/DomainSpecificLanguage.html )

    [2] http://martinfowler.com/dslwip/UsingDsls.html#DefiningDomainSpecificLanguages

    ОтветитьУдалить
  24. @mkovrov

    У меня представления о DSL как о языке, который отличается от хост языка. swizard привёл два таких классических для CL примера - это loop и format. ASDF же наоборот, типичный CL. DSL вводят как язык, который лучше подходит для решаемой задачи. Поэтому нельзя говорить, что между API и DSL - тонкая грань, это просто разные вещи.

    С каких пор вообще такой попсовик как Фаулер стал в авторитете?

    ОтветитьУдалить
  25. В "представлении о DSL как о языке, который отличается от хост языка" Common Lisp не даёт ничего уникального в плане DSL.

    Как раз приспособление DSL под S-выражения и совместное использование DSL и CL -- это то, о чём все говорят, когда хвалят возможности лиспа в этом плане.

    ОтветитьУдалить
  26. > Common Lisp не даёт ничего уникального в плане DSL.

    Так и есть. В CL просто проще реализовывать eDSL, но не более того.

    > приспособление DSL под S-выражения и совместное
    > использование DSL и CL

    Отлично, но не любое s-выражение определяет DSL, а тот же format обходиться и без s-выражений. s-выражения могу использоваться для создания eDSL, а могу и не использоваться. s-выржения это просто характерный для CL инструмент.

    > чём все говорят, когда хвалят возможности лиспа
    > в этом плане.

    Что-то хвалить, на мой взгляд, можно только на основе результатов практического использования. В open source проектах на CL DSL практически не используются. Так что это просто демагогия.

    Вот в Haskell, как мне показалось, DSL действительно используются достаточно широко и там есть соответствующая интсрументальная поддержка.

    ОтветитьУдалить