Уникальные технологии Common Lisp (с примерами использования)

Базовые подсистемы языка

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

Что это за технологии и какие возможности дает их использование?

Макросистема

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

Примеры применения:


  1. Определение управляющих конструкций языка, которые могут использоваться на равне со стандартными (на самом деле практически все стандартные управляющие конструкции также являются макросами. Основу языка — «аксиомы», которые невозможно определить через другие конструкции — составляют специальные операторы). В качестве примера можно привести анафорические управляющие конструкции (см. библиотеку Anaphora), которые, используя принцип «convention over configuration», скрывают реализацию некоторых типичных шаблонов.


    Самый простой пример — макро AIF (или IF-IT), которое тестирует первый аргумент на истинность и одновременно привязывает его значение к переменной IT, которую, соответственно, можно использовать в THEN-clause:


    (defmacro aif (var then &optional else)
      `(let ((it ,var))
        (if it ,then ,else)))

    Учитывая то, что в CL ложность представляется константой NIL, которая также соответствует пустому списку, такая конструкция, например, часто применяется в коде, где сначала какие-то данные аккумулируются в список, а потом, если список не пуст, над ними производятся какие-то действия. Другой вариант, это проверить, заданно ли какое-то значение и потом использовать его:


    (defun determine-fit-xture-type (table-str)
      "Determine a type of Fit fixture, specified with TABLE-STR"
      (handler-case
          (aif (find (string-trim *spacers* (strip-tags (get-tag "td" (get-tag "tr" table-str 0) 1)))
                     *fit-xture-rules* :test #'string-equal :key #'car)
               (cdr it)
               'row-fit-xture)
        (tag-not-found () 'column-fit-xture)))

    * В этой функции проверяется, есть ли во второй ячейке первой строки HTML таблицы какие-то данные и в соответствии с этим определяется тип привязки для Fit-теста. Переменной it присвоены найденные данные.

  2. Создание DSL’ей для любой предметной области, которые могут иметь в распоряжении все возможности компилятора Common Lisp. Ярким примером такого DSL’я может служить библиотека Parenscript, которая реализует кодогенерацию JavaScript из Common Lisp. Используя ее, можно писать макросы для Javascript!


    (js:defpsmacro set-attr (id attr val)
      `(.attr ($ (+ "#" ,id)) ,attr ,val))

    * Простейший макрос-обертка для задания аттрибутов объекта, полученного с помощью селектора jQuery

  3. В форме локальных макросов (MACROLET) для модуляризации и разделения потоков вычислений внутри сложных функций, а также для соблюдения принципа DRY при написании лишь слегка отличающегося кода в различных местах одной функции.
  4. Наконец, создание инфраструктурных систем языка. Например, с помощью макросов можно реализовать продления (библиотека CL-CONT), ленивые вычисления (библиотека SERIES) и т. д.
  5. ... ну и для многих других целей.

Больше по теме: Paul Graham, On Lisp

Мета-объектный протокол и CLOS

  • Основа объектной системы языка. Позволяет манипулировать представлением классов.
  • Методы не принадлежат классам, а специализируются на них, что дает возможность элегантной реализации множественной диспетчиризации. Также возможна специализация не по классу, а по ключу.
  • Уникальной является технология комбинации методов, позволяющая использовать стандартные способы комбинации: перед, после, вокруг, —, а также определенные пользователем.

Примерами использования мета-объектного протокола также являются инфраструктурные системы языка, реализованные в виде библиотек:

  • object-persisance: Elephant, AllegroCache
  • работа с БД: CLSQL
  • интерфейс пользователя: Cells

Библиотека CLSQL создана для унификации работы с различными SQL базами данных. Кстати, на ее примере можно увидеть проявление мультипарадигменности Common Lisp: у библиотеки есть как объектно-ориентированный интерфейс (ORM), реализованный на основе CLOS, так и функциональный (на основе функций и макросов чтения).


С помощью мета-объектного протокола стандартный класс языка расширяется специальным параметром — ссылкой на таблицу БД, к которой он привязан, а описания его полей (в терминологии Lisp: слотов) — дополнительными опциональными параметрами, такими как: ограничение уникальности, ключа, функция-преобразователь при записи и извлечении значения из БД и т. д.

Больше по теме: Gregor Kiczales et al. The Art of Metaobject Protocol

Система обработки ошибок / сигнальный протокол

Система обработки ошибок есть в любом современном языке, однако в CL она все еще остается в определенном смысле уникальной (разве что в C# сейчас вводится нечто подобное). Преимущество этой системы заключается опять же в ее большей абстрактности: хотя основная ее задача — обработка ошибок, точнее исключительных ситуаций, — она построена на более общей концепции передачи управления потоком выполнения программы по стеку... Как и системы в других языках. Но в других языках есть единственный предопределенный вариант передачи управления: после возникновения исключительной ситуации стек отматывается вплоть до уровня, где находится ее обработчик (или до верхнего уровня). В CL же стек не отматывается сразу, а сперва ищется соответствующий обработчик (причем это может делаться как в динамическом, так и в лексическом окружении), а затем обработчик выполняется на том уровне, где это определенно программистом. Таким образом, исключительные ситуации не несут безусловно катастрофических последствий для текущего состояния выполнения программы, т. е. с их помощью можно реализовать различные виды нелокальной передачи управления (а это приводит к сопроцедурам и т. п.) Хорошие примеры использования сигнального протокола приведены в книге Practical Common Lisp (см. ниже).

Больше по теме:

Вспомогательные технологии

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

Протокол множественных возвращаемых значений

Дает возможность возвращать из функции несколько значений и по желанию принимать все их (и привязывать к каким-то переменным) или только часть. По-умолчанию для кода, не использующего эту функциональность, передается только 1-е значение.

Казалось бы, это простая возможность, однако, на поверку, она требует обширной поддержки на языковом уровне (учитывая необходимость поддержки возврата из блоков и т. п.).

Протокол обобщенных переменных

Это аналог свойств в некоторых ОО-языках. Концептуально, оперирует понятием места (place) — по сути дела ячейки памяти, однако не физической (без манипуляции указателями) — это может быть просто объект или же элемент какой-то структуры (будь-то опять же объект, список, массив и т. д.) Таким образом, имеются намного большие возможности, чем при использовании обычных свойств, поскольку для любой функции, которая читает значения какого-либо места, можно указать функцию которая его значение задает.

Больше по теме: Paul Graham, On Lisp, Ch.12 «Generalized Variables»

Макросы чтения

Это инструмент модификации синтаксиса языка за пределы s-выражений, который дает программисту возможность, используя компилятор Lisp, создать свой собственный синтаксис. Его работа основана на фундаментальном принципе Lisp-систем: разделении времени чтения, времени компиляции и времени выполнения — REPL (Read-Eval-Print Loop). Обычные макросы вычисляются (раскрываются, expand) во время компиляции, и полученный код компилируется вместе с написанным вручную. А вот макросы чтения выполняются еще на этапе обработки программы парсером при обнаружении специальных символов (dispatch characters). Механизм макросов чтения является возможностью получить прямой доступ к Reader’у и влиять на то, как он формирует абстрактное синтаксическое дерево из «сырого» программного кода. Таким образом, можно на поверхности Lisp использовать любой синтаксис, вплоть до, например, C-подобного. Впрочем, Lisp-программисты предпочитают все-таки префиксный унифицированный синтаксис со скобками, а Reader-макросы используют для специфических задач.

Пример такого использования — буквальный синтаксис для чтения hash-таблиц, который почему-то отсутствует в спецификации языка. Это, кстати, еще один пример того, каким образом CL дает возможность изменить себя и использовать новые базовые синтаксические конструкции наравне с определенными в стандарте. Основывается на буквальном синтаксисе для ассоциативных списков (ALIST):

<code class="lisp">; a reader syntax for hash tables like alists: #h([:test (test 'eql)] (key . val)*)
(set-dispatch-macro-character #\# #\h
     (lambda (stream subchar arg)
       (declare (ignore subchar)
                (ignore arg))
       (let* ((sexp (read stream t nil t))
              (test (when (eql (car sexp) :test) (cadr sexp)))
              (kv-pairs (if test (cddr sexp) sexp))
              (table (gensym)))
         `(let ((,table (make-hash-table :test (or ,test 'eql))))
            (mapcar #'(lambda (cons)
                        (setf (gethash (car cons) ,table)
                              (cdr cons)))
                    ',kv-pairs)
            ,table)))))</code>

Больше по теме: Doug Hoyte, Let Over Lambda, Ch.4 «Read Macros»

Послесловие

В заключение хотелось бы коснуться понятия высокоуровневого языка программирования. Оно, конечно, является философским, поэтому выскажу свое мнение на этот счет: по-настоящему высокоуровневый язык должен давать программисту возможность выражать свои мысли, концепции и модели в программном коде напрямую, а не через другие концепции, если только те не являются более общими. Это значит, например, что высокоуровневый язык должен позволять напрямую оперировать такой сущностью, как функция, а не требовать для этого задействовать другие сущности такого же уровня абстракции, скажем, классы. Подход к созданию высокоуровневого языка можно увидеть на примере Common Lisp, в котором для каждой задачи выбирается подходящая концепция, будь то объект, сигнал или место. А что дает нам использование по-настоящему высокоуровневых языков? Большую расширяемость, краткость и адаптируемость программы к изменениям, и, в конце концов, настоящую свободу при программировании!

  • Популярное

48 комментариев

Подписаться на комментарииОтписаться от комментариев Комментарии могут оставлять только пользователи с подтвержденными аккаунтами.

А еще: 1. Lamkins D.B. Successful Lisp.How to understand and use Common Lisp.20012. Harold Abelson, Gerald Sussman, Julie Sussman, Harold Abelson, Julie Sussman — Structure and Interpretation of Computer Programs (там, правда, Sheme идет, кажется) все хорошо гуглится и ибуглится (eboogle.ru)

2 rmrnch: Спасибо за книги — хорошая библиография. С 2-мя книгами по CLOS’у Bobrowa и ко. не приходилось сталкиваться раньше.

Список книг по Ліспу з моєї бібліотечки: 1. Franz Inc. ANSI Common Lisp — 2002, довідник.2. Sonya Keene. Object-oriented programming in Common Lisp: A Programmer’s guide to CLOS — 1989, 290p.3. Э. Хювёнен, И. Сеппянен. Мир Лиспа. Т1: Введение в язык Лисп и функциональное программирование, 458с.4. Т2 тих же авторів5. Peter Seibel. Practical Common Lisp — 2005, 528p.6. An Introduction to Programming in Emacs Lisp. Second Edition by Robert J. Chassell — 2002, 314p.7. CLOS in Context: The Shape of the Design Space. Daniel G. Bobrow, Richard P. Gabriel, Jon L White — 2004, 34p.8. CLOS: Integrating Object-Oriented and Functional Programming. Ті ж автори, 2004, 20с.9. Common Lisp Object System Specification. Authors: Daniel G. Bobrow, Linda G. DeMichiel, Richard P. Gabriel, Sonya E. Keene, Gregor Kiczales, and David A. Moon — 1988, збірник документів.10. Basic Lisp Techniques. David J. Cooper, 2003 — 100с.11. Tutorial on Good Lisp Programming Style. Peter Norvig, Kent Pitman — 116p.12. On Lisp. 426с.13. COMMON LISP: An Interactive Approach. STUART C. SHAPIRO — 1991, 426с.14. Common Lisp Language Specification — 1096p. ІМНО, найбільш корисна на практиці — як щось не ясно в мові, то однозначно сюди:) + всілякі Idiot’s Guides, коммон лісп кукбук та дещо з того, на що вказує сайт SBCL Internals. Не скажу, що прочитав ті 14 книжок повністю, я більше з точки зору практики підходив — читав, що потрібно для проекту. Збирав я ці книжки в неті вже давненько, тому посилань дати не зможу, але всі вони гугляться.Про компанію — називати не буду, тим більше Лісп в ній не головний напрямок, але вона в Україні.Щоб згадати в пості С++:), зроблю невелику рекламу одній книзі — «Шаблони С++. Довідник розробника», автори Вандевурд, Джосаттіс. Зараз читаю, і просто приємно, коли вміють люди так доступно розписати складні теми. І весело буває — «шаблонний параметр шаблонного аргумента шаблона» — здається, цілком згодиться як скоромовка:)

2 rmrnch: Все-таки хотелось бы узнать про компанию, в которой можно посмотреть на большое количество Common Lisp кода (или вы не в Украине?), а также узнать какие именно 10 книг вы прочитали по Lisp’у. Просто я, например, в 21 год о нем вообще не слышал еще, если не считать разве что беглого упоминания в книге Гради Буча — так что, честно говоря, поражает уровень вашей осведомленности уже сейчас (не сочтите за недоверие).

В мене ще жодного разу не було такого відчуття, що Лісп має якусь унікальну і особливу, принципову перевагу над С++.

Можливість поступово допрограмовувати працюючу систему (здається «exploratory programming»): тобто, можливість вносити зміни у вже працюючу систему без необхідності перезапускати її під час розробки, а також можливість випробування нового коду одразу в REPL. Та і можливості розширення мови, мені здається, на порядок ширші за C++.GNU Emacs + SLIME створюють досить таки приємне середовище для «інтерактивного програмування», що, гадаю, принципово неможливо для C++ та подібних, чітко компільованих мов програмування.В той же час, можливість компіляції Common Lisp програм в машинні коди є, на мій погляд, цікавою перевагою у порівнянні з іншими динамічними (скриптовими) мовами програмування (тобто, для яких ще не створено реалізації з відповідними можливостями).

Я не стверджую, що С++ є абсолютно універсальним. Вище вже писав — кожній задачі свій інструмент. Я не впевнений також, що маю право розмірковувати тут про те, чим С++ краще Ліспа, оскільки мої знанння С++ досить посередні, і мабуть на форумі є люди, здатні розкрити цю тему переваг С++ більш глибоко. Проте я спробую, і якщо десь помиляюсь, то вкажіть мені на це. Справа в тому, що багато чого з функціонального програмування та конкретно Ліспа є і в С++ та його бібліотеках. Шаблони і метапрограмування широко використовуються, і я наразі не можу точно сказати, що є такого в Ліспі, чого немає в сучасних бібліотеках для С++ (а частина цих можливостей — новий стандарт С++). Моя впевненість в тому, що С++ не відстає, грунтується на існуванні таких бібліотек, як Boost.Any, Boost.Bind, Boost.Enable If, Boost.Exception, Boost.Foreach, Boost.Function, Boost.Fusion, Boost.Lambda, Boost.MPL, Boost.Optional, Boost.Parameter, Boost.Statechart, Boost.Tuple. В документації до них інколи спеціально пишуть, що дана бібліотека забезпечує реалізацію такої-то концепції функціонального програмування в С++, і мені ці реалізації подобаються деколи більше, ніж оригінали (просто люблю С++:)). В мене ще жодного разу не було такого відчуття, що Лісп має якусь унікальну і особливу, принципову перевагу над С++. А коду на Ліспі я бачив багато, і написаного непоганими фахівцями. Так, іноді він красивий і ефективний, але не кращий «в середньому». А от Вам простий приклад недоліку Ліспа: уявіть собі, що Вам необхідно реалізувати власну систему управління пам"яттю для великого проекту. В Ліспі про збір сміття взагалі не думають, там все бере на себе середовище. А в С++ я можу перевизначити оператор new і мати власну систему збору сміття, яка, наприклад, може попереджати мене про те, що ще 100МВ, і пам"ять закінчиться, а тому користувачу варто зберегти проект. В Ліспі нестача пам"яті — це крах, SBCL каже no more memory і його більше нічого не обходить. Сам бачив:) Тому я думаю, що С++ синтезує в собі багато ідей з різних областей програмування, і непогано балансує між низькорівневістю і абстракцією. Хоча ще раз кажу, це всього лише моя особиста думка, помножена на залишки юнацького максималізму (мені 21). Можливо з віком і новим досвідом я зміню думку, але поки що я вважаю, що С++ і йому подібні мови не просто так популярні, а через ті самі об"єктивно існуючі переваги, про які ми тут так довго і багато балакали:) P.S. Про Пітера Норвіга — коли паттерн реалізується середовищем чи є його частиною, то це зручно, але особисто мені подобається самому закодати і розуміти, як все працює, а не покладатись на закриту реалізацію. Крім того, тій презентації вже 10 років, за той час дещо змінилось і переваги динамічних мов не видались мені вирішальними після прочитання. Словом, все це дуже суб"єктивно, і можливо, мені ще просто рано брати участь в таких фундаментальних дискусіях.

Коли говорити про методи розробки для складних систем, то С++ та Java мають таку перевагу, як масовість використання. І відповідна кількість документації для всіх етапів розробки — від загального OOD/OOA до конкретної реалізації того чи іншого паттерна і оптимізації коду. Лісп потребує деякої наполегливості в освоєнні -, а немало книг по ньому переважно не підіймаються вище того, як зробити хитру рекурсію або оптимальний алгоритм чого-небудь, а також як написати макрос, який буде генерити нам макрос, який в свою чергу буде... Про загальну архітектуру пишуть менше, якщо взагалі пишуть...Якщо така книга чи ресурс Вам відомі, поділіться посиланням, буду вдячний:)

1. В практической части PCL (которую, я уверен, вы читали) достаточно много времени уделяется правильному подходу к созданию сложных систем (хотя и в неявной форме). Еще на интересующую вас тему есть книга Patterns of Software: Tales from the Software Community Ричарда Гэбриэла (основателя того самого Lucid). Ее я не читал, но читал некоторые из его эссе. Во всяком случае в западных кругах он считается авторитетом в области Software Design.Не забывайте также, что очень многие исследования в этой области начинались с Lisp представителями Lisp-сообщества и потом распространились далее. Взять, к примеру, того же Gregor Kiczales’а, который написал AMOP, а также предложил аспектно-ориентированное программирование.2. Кроме того, в целом, ничего не мешает вам применять знания о Software Engineering & Design, программируя на разных языках — они ведь, по идее, должны быть языково-независимыми, так ведь? Просто в некоторых языках пресловутые паттерны приходится программировать в явной форме, а в некоторых они реализованы в самой языковой среде.3. Ну и, наконец, к вопросу о самих паттернах/OOD/OOD.etc. и их фундаментальности. Я уверен, что вам также знакома и презентация Питера Норвига, в которой он показывает, что добрая половина из них являются просто костылями, придуманными для обхода искусственных ограничений статических языков. По моему мнению, лучшие учебники по Software Design — не те, которые учат, как запрограммировать ту или иную конструкцию в каком-то конкретном языке, а те, которые демонстрируют фундаментальные (языконезависимые) подходы к разработке и развитию сложных систем от малого к большому (а это такие книги как: SICP, PAIP или On Lisp).

Про залежність від Лісп-хоста — я маю на увазі перенесення системи з одної реалізації Ліспа на іншу. Мені доводилось портувати код з Lucid в SBCL, тут і повилазили всі ці низькорівневі біндінги і виходи за межі стандарту.

Я понял. Ну, это тема для отдельной дискуссии. Опять же, как по мне, это смотря как повернуть.

Я розумію, що в кожної мови своя ніша, і напевно несправедливо вимагати від Ліспа бути придатним для всіх. З іншого боку, коли люди пишуть компілятор в байт код і віртуальну машину для цього коду на Ліспі (а таке є в мене на проекті), то я маю право запитати: ну чим С++ гірше для конкретно цієї задачі? Це саме той випадок, коли маєш молоток і починаєш вважати все цвяхами.

В целом, вы тоже, наверное, будете смеяться, но вы — уже 2-й человек, который говорит мне про возможность использования Lisp только в узко ограниченном кругу задач, причём практически одними и теми же словами. На счет драйверов я с вами, конечно, соглашусь (если это не драйвера под Lisp-машину:)), но все остальное... Я, например, как раз использую Lisp под веб (поскольку веб — это только интерфейс), читал о том, как его используют и для embedded (например, для программирования FPGA или генерации asm-кода для каких-то специализированных устройств). Я не знаю специфики той задачи, пример которой вы приводите — это тоже тема для более глубокой дискуссии (можем обсудить по почте), —, но если вы спрашиваете, чем С++ хуже, то тогда ответьте хотя бы, чем он лучше?:) Просто, интересно понять на чем основанна увереноость, что С++ — это молоток для любых гвоздей (притом, что Лисп — нет)?

Коли говорити про методи розробки для складних систем, то С++ та Java мають таку перевагу, як масовість використання. І відповідна кількість документації для всіх етапів розробки — від загального OOD/OOA до конкретної реалізації того чи іншого паттерна і оптимізації коду. Лісп потребує деякої наполегливості в освоєнні -, а немало книг по ньому переважно не підіймаються вище того, як зробити хитру рекурсію або оптимальний алгоритм чого-небудь, а також як написати макрос, який буде генерити нам макрос, який в свою чергу буде... Про загальну архітектуру пишуть менше, якщо взагалі пишуть. Хорошими програмістами стають, коли є в кого повчитися, грамотний код почитати. Не кажу, що перечитав багато по Ліспу (книг 10 набереться), але щось не зустрічалось мені такої книги, як дизайн і архітектура великих систем на Ліспі (або про їх реалізацію без «звичного» ООП, як його розуміють для того ж С++). Де б писали про грамотне розділення системи на ядро, зовнішні інтерфейси, управління пам"яттю, управління мультизадачністю, синхронізацію та інше. Тобто про те, що потрібно на практиці. З прикладами коду. Якщо така книга чи ресурс Вам відомі, поділіться посиланням, буду вдячний:) Я розумію, що в кожної мови своя ніша, і напевно несправедливо вимагати від Ліспа бути придатним для всіх. З іншого боку, коли люди пишуть компілятор в байт код і віртуальну машину для цього коду на Ліспі (а таке є в мене на проекті), то я маю право запитати: ну чим С++ гірше для конкретно цієї задачі? Це саме той випадок, коли маєш молоток і починаєш вважати все цвяхами. Внести в цю машину найменшу зміну вкрай важко — треба пильнувати, щоб все не розвалилось. Тести тут мало помагають, бо теоретично на вхід машини може прийти безконечна кількість тих чи інших результатів від компілятора. Крім того, машина використовує глобальні контексти і багатопотоковість обчислень. Написати для неї осмислені тести не легше, ніж її саму:) Про залежність від Лісп-хоста — я маю на увазі перенесення системи з одної реалізації Ліспа на іншу. Мені доводилось портувати код з Lucid в SBCL, тут і повилазили всі ці низькорівневі біндінги і виходи за межі стандарту. Щоб заімплементити колбек з С в Лісп, довелось лізти в сорси SBCL і з"ясовувати, як же воно все влаштовано. Ви будете сміятись, але мене виручило знання асма — там ці колбеки якраз використовують деяку кросплатформенну надбудову над ним, яка потім вже транслюється в машинний код. Називається це діло VOP — Virtual Operation, наскільки я пам"ятаю. Взагалі SBCL Internals не порадував, можна б було трохи більше розписати, щоб стороннім легше було в код в"їхати. Щось я багато написав, пора завершувати. В підсумку, мабуть, залишимось при старому правилі — кожній задачі свій інструмент. І Лісп хороша річ, і С++ достойна штука. І взагалі, головне — як уявляєш собі задачу та її модель, а на чому імплементити — діло десяте. Хіба є спеціальні вимоги — web, ембеддед, драйвери — тоді вибір інструментів дещо звужується:)

2 rmrnch: Мне кажется, вы сами объяснили, в чем проблема c вашей Common Lisp системой:

Система написана в “кращих” традиціях функціонального програмування — всі 50 МВ сорсів належать до одного пакету, чітка архітектура відсутня, про паттерни навіть не згадую... Сам код макаронний, все залежить від всього, і зміни в одному місці можуть аукнутись там, де цього ніхто не чекав. Тестами це все не покрито, бо в часи написання системи TDD ще був не в моді, та й взагалі складається враження, що писали систему без чіткого розуміння того, які недоліки має Лісп.

Проблема не в использовании Лиспа, а в не владении разработчиками грамотным подходом к разработке. (Вы думаете, что при таком же подходе к разработке на С++ получилось бы что-то более поддерживаемое?) Да и, вообще говоря, парадигма функционального программирования (да и метапрограммирования) ортогональны к технологиям организации разработки, а тем более никак не подразумевает она того, чтобы “всі 50 МВ сорсів належать до одного пакету, чітка архітектура відсутня” и т.п.:)

Додайте сюди залежність від хост-Ліспа (а при роботі з викликами в С з Ліспа та особливо з колбеками з С в Лісп ця залежність встає на повен зріст)

Мне кажется, это скорее проблема зависимости С/С++ от хоста. Я так понимаю, вы говорите про независимость от хоста, которая есть у Java — тогда можно использовать ABCL компилятор (например). (Но причем тут сравнение с С++?)

Складність і кучерявість Ліспа в купі з відсутністю нормальної типізації та загальною атмосферою вседозволеності, яка пронизує специфікацію Ліспа, вилазять йому в цьому випадку боком.

Вместо Лиспа здесь можно вставить любой динамический язык (Python, Ruby, Javascript...). А в ответ услышать примерно такого же уровня утверждение про статические языки.:)

SBCL. Останній, до речі, далеко не фонтан, мені доводилось розбиратись в його коді і навіть трохи правити. Пам"ять SBCL їсть нехило — при компіляції складних макросів може спокійно взяти собі 1500 МВ оперативи, не всяка 3Д забавка стільки бере. Все тому, що там використовується багатократна оптимізація коду на проміжних етапах компіляції... Кожен прохід на великому макросі — +100М-150М меморі, я міряв спеціально засобами самого компілятора. Збирач сміття деколи любить вивалитись з еррором, і цей баг ще й зараз в розсилці по SBCL мелькає. Тому я схильний вважати цей компілятор нестабільним.

Обратите внимание, что память он ест на этапе компиляции и за счет этого экономит память и процессорное время на этапе исполнения. По-моему, это разумный trade-off при оптимизации. А что касается проблемы с GC — я и сам с ней встречался (вот более подробное исследование: http://blo.udoidio.info/2008/1...), и работа по исправлению ведется. Думаю, это проблема никак не связана со спецификой Common Lisp’а — такие ошибки могут быть у всех: мне, например, рассказывали про баг в.Net GC, из-за которого он может подвисать на несколько минут, и этот баг тоже пока не исправлен (не говоря о том, что никто не пропагандирует SBCL как идеальный или из ряда вон выходящий компилятор).P.S. Если это не промышленный секрет, хотелось бы узнать название организации, где занимаются разработкой и поддержкой Common Lisp систем (к которой вы имеет отношение).

Маючи певний досвід розробки на Ліспі, хотів би зазначити, що для великих проектів він не такий зручний, як той же С++ чи Java. Тонкощі програмування і код, який модифікує сам себе на ходу, відступають перед незручностями відладки і розуміння. За реальним прикладом ходити далеко не треба — в мене на проекті є система моделювання і управління виробництвом, написана майже повністю на CommonLisp. Приблизний розмір сорсів складає 50МВ — для такої мови, як Лісп, де у двадцяти рядках можна мати стільки семантики, скільки на деяких мовах не влізе і в 100−150, розмір гігантський. Система написана в «кращих» традиціях функціонального програмування — всі 50 МВ сорсів належать до одного пакету, чітка архітектура відсутня, про паттерни навіть не згадую. В коді багато великих макросів, для багатьох з них результат виконання macroexpand не влазить на кілька сторінок А4 маленьким шрифтом, є навіть один мегамакрос, який розкривається в 100К веселого коду. Сам код макаронний, все залежить від всього, і зміни в одному місці можуть аукнутись там, де цього ніхто не чекав. Тестами це все не покрито, бо в часи написання системи TDD ще був не в моді, та й взагалі складається враження, що писали систему без чіткого розуміння того, які недоліки має Лісп. Технологічно система крута — має власний компілятор, віртуальну машину, менеджер пам«яті, перехідники в натівні ліби дозволяють їй мати нормальний юай, і все це написано на Ліспі. Але сама якість коду при цьому вийшла кепською якраз через надто активне захоплення макросами і самомодифікацією. Додайте сюди залежність від хост-Ліспа (а при роботі з викликами в С з Ліспа та особливо з колбеками з С в Лісп ця залежність встає на повен зріст), і неспроможність Ліспа бути основою справді великих програмних систем стає очевидною. Складність і кучерявість Ліспа в купі з відсутністю нормальної типізації та загальною атмосферою вседозволеності, яка пронизує специфікацію Ліспа, вилазять йому в цьому випадку боком. Одна справа — вирішити на Ліспі якусь проблему «академічного» характеру, порадіти красі коду і почепити в рамочку на стіну, а інше — написати багаторівневу переносну аплікацію, яку легко супроводжувати і розширяти. Тому я вважаю, що той же С++ в умілих руках дасть кращий результат на виході, крім того, в ньому теж є багато чого з світу Ліспа — Boost розвивається не менш активно за SBCL. Останній, до речі, далеко не фонтан, мені доводилось розбиратись в його коді і навіть трохи правити. Пам"ять SBCL їсть нехило — при компіляції складних макросів може спокійно взяти собі 1500 МВ оперативи, не всяка 3Д забавка стільки бере. Все тому, що там використовується багатократна оптимізація коду на проміжних етапах компіляції, і проходи оптимізатора далі сьомого рідко дають ефект, а кількість проходів там захардкоджена числом 10. Кожен прохід на великому макросі — +100М-150М меморі, я міряв спеціально засобами самого компілятора. Збирач сміття деколи любить вивалитись з еррором, і цей баг ще й зараз в розсилці по SBCL мелькає. Тому я схильний вважати цей компілятор нестабільним. Можливо це суб"єктивно, але мені здається, що особливих причин писати на Ліспі зраз немає — я можу так казати хоча б тому, що досить багато на ньому писав і пишу на роботі, але з двох мов — С++ та Лісп — я б вибрав першу. Можливо їх порівняння взагалі некоректне, бо це різні парадигми програмування, але все врешті-решт зводиться до практичних проблем та їх моделювання — і я не можу пригадати жодної такої проблеми (NP-повні не в рахунок:)), яка б не вирішувалась так чи інакше в рамках С++. Добре писати на С++, розбираючись при цьому в нюансах, набагато складніше, ніж на Ліспі по списках бігати і самомодифікацією займатися. А ось проектування і дизайн однаково складні для обох мов. Все вищесказане є лише моя точка зору, і можливо я дещо применшую тут можливості та користь від Ліспа, але я відштовхуюсь при цьому від практики, а також власних вражень від нього. Цікаво було б почути думку людей, які застосовували на великих проектах Лісп — чи все їх в ньому влаштовує?

Виктор, да что вы, конечно если что-то есть в Лиспе, то этого нет больше нигде. Круче только вареные яйца Тюринга.

В языке Common Lisp есть как минимум 3 инфраструктурных технологии, во многом формирующие подходы к его применению, которые в других языках либо отсутствуют вовсе, либо реализованы в очень ограниченном варианте, либо еще мощнее чем в LISP, только не говорите лисперам, могут озвереть.

А конкретно: які саме, і де? Було б цікаво дізнатись, для розширення свого світогляду, так би мовити...P.S.: LISP тут вживається замість Common Lisp, правильно?

В языке Common Lisp есть как минимум 3 инфраструктурных технологии, во многом формирующие подходы к его применению, которые в других языках либо отсутствуют вовсе, либо реализованы в очень ограниченном варианте, либо еще мощнее чем в LISP, только не говорите лисперам, могут озвереть.


На самом деле можно сказать REAK-Rules был бы возможен в любом случае

Вспомним, хотя бы, Тюринга.:)

Именно! Именно!

На самом деле можно сказать REAK-Rules был бы возможен в любом случае

Конечно. Вспомним, хотя бы, Тюринга.:) Весь интерес в деталях.

В общем в Python реализация GF не требует «интеграции в языковую среду», а отлично живет как библиотека.

Что, безусловно, характеризует Python как высокоуровневый язык.Собственно с прицелом на то, чтобы коснуться подобных вопросов и была написана статья. Ну, а «уникальность», безусловно, понятие относительное, что ни в коем случае, на мой взгляд, не дисквалифицирует его в качестве названия для статьи...Ну, вроде как, со всеми вопросами разобрались.

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

Я говорил только о том, что плохо если требуется что-то изучать и вообще трудиться для ограничения сложности. Лучше когда это нужно только для её увеличения.

Но Вы же сами говорите: в PEAK-Rules есть и предикатная диспетчиризация и любой сложности комбинация-методов. Так потому ее в CLOS и нет, чтобы не усложнять простую концепцию. Хотя инструмент для создания этих продвинутых вещей предусмотрен — MOP.

Отлично! Вот дискуссия и привела к еще одной принципиальной особенности Python-way которую давно пытался найти как обьяснить. Завтра постараюсь написать.

Декораторы супер-простая штуковина, синтаксический подсластитель. На самом деле можно сказать REAK-Rules был бы возможен в любом случае, но отдельные свойства конечно полагаются на какие-то возможности языка (или, иногда, именно CPython). Он достаточно быстр благодаря компиляции байт-кода на лету (возможность создавать code-objects). Сложные предикаты задаются достаточно просто благодаря синтаксическому разбору и тому что возможна интроспекция функций (для получения имен аргументов). Комбинация происходит благодаря простым соглашениям и отчасти тому, что P-R могут быть применены к собственным внутренностям. Апгрейдить обычные функции до GF можно благодаря тому что функции хранят свой код (code-object) в атрибуте который можно и читать и писать. Т.е. по сути, если язык достаточно динамичен, то всё необходимое для мощных GF в нем есть.

Насколько это дополнение может быть интегрированно в языковую среду (я имею в виду не случаи непосредственного примененния в прикладном программировании aka библиотека, а то, как она сочетается с использованием других технологий языка, и как на их основе можно создать что-то большее), мне не понятно. Поэтому говорить, что такая технология есть в языке — нельзя.

Технологии в языке действительно нет. Что, я повторюсь, является его плюсом, и еще раз показывает что плохому танцору BDFL мешает. Конфликтов или несовместимостей PEAK-Rules с чем либо даже не могу представить. P-R обитает в своей парафии и никуда не вмешивается. Более того, очень весело можно расширять GF обьявляя ООП-метод в классе. Что называется и нашим и вашим.

@abstractdef getfoo(bar):    passclass C(object):    @when(getfoo) # тут это означает @when(getfoo, (C,))    def getfoo_method(self):        ......

Имена getfoo / getfoo_method разные только чтобы не путаться в примере.В общем в Python реализация GF не требует «интеграции в языковую среду», а отлично живет как библиотека.

Вот насчет того что про милиционеров — философия не согласен. Потому что я сам хоть и умею пользоваться например GF или еще какой мудреной штукой, но при разработке я принимаю решение использовать ли её или нет чтобы быть увереным в дальнейшем что какой-то код делает нечто определенное и не более того, с какими-то гарантиями.

Этот подход, безусловно, можно применять, используя Лисп.Далее, понятно, что Пайтон-программист скажет, что он лучше всего понимает и уверен в технологиях, знакомых ему по Пайтону, но в то же время даже С+±программисту уже будет с ними не так комфортно. Увидит он, например, что нужно в явном виде задавать self и испугается.:) (Хотя ничего сложного в этом нет). Так же и с GF: пока мы не лезем в дебри предикатной диспетчиризации — это, вообще, всего навсего более простой аналог методов в классическом ООП (не путать: сложный и непривычный). Но Вы же сами говорите: в PEAK-Rules есть и предикатная диспетчиризация и любой сложности комбинация-методов. Так потому ее в CLOS и нет, чтобы не усложнять простую концепцию. Хотя инструмент для создания этих продвинутых вещей предусмотрен — MOP.

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

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

В общем, в Lisp’е все то же, что делается во время компиляции, можно делать и во время исполнения (механизм GF, во всяком случае, полностью динамичен). Однако, я не совсем понял, что Вы подразумеваете под «расширением GF замыканиями»

Я имел в виду использование замыкания в качестве тела GF-метода. Если там всё динамично, значит можно конечно.

И еще. Я, может быть, не достаточно четко выразился во фразе «Я говорю о технологиях, которые являются в своем роде лучшим решением, и важно, есть они в других языках или нет.» Скажу по другому, в Python нет технологии мета-объектного протокола (манипуляции представлением объектов) и нет технологии комбинации методов и множественной диспетчиризации. Зато есть технология декораторов, с помощью которой реализована множетсвенная диспетчиризация как дополнение к языку. Насколько это дополнение может быть интегрированно в языковую среду (я имею в виду не случаи непосредственного примененния в прикладном программировании aka библиотека, а то, как она сочетается с использованием других технологий языка, и как на их основе можно создать что-то большее), мне не понятно. Поэтому говорить, что такая технология есть в языке — нельзя.

Я всё время отрицаю потому что обсуждение обычно идет именно про разногласия, но в то же время со многим я согласен, иначе не заводил бы разговор. Лисп — хороший, а мой тезис это то, что многие из его интересных возможностей не уникальны, и в то же время некоторые недостатки (субьективные если хотите) отсутствуют в других языках.

Сергей, Вы так увлеклись отрицанием всего, что я говорю, что даже придумали мне опечатку. Опечатки нет, я именно это и имел в виду.

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

Я говорю о технологиях, которые являются в своем роде лучшим решением, и важно, есть они в других языках или нет.

Следует ли понимать название статьи как " [Исключительно мощные] технологии [произошедшие из] Common Lisp«? Если так, то не имею возражений.

Иногда нужна просто функция. Иногда нужен просто метод.

В Lisp’е все это есть, да и вы сами это, вроде как, должны знать (если писали о том, что нельзя превратить обычную функцию в generic).

Путаница вышла. В данном случае я имел в виду «классический-ООП-метод».

Но проблема дублирования информации при определении метода остается.

Вы о доступе к атрибутам через self.*? Категорически не согласен что это проблема, по-моему никто даже не предлагает от этого избавиться. Если о self среди аргументов, то я не понял с чем вы тогда согласились.

На счет Микелянджело, милиции и чайников — это уже чистая философия и спорить тут бесполезно.

Я не спорю и не склоняю к своей точке зрения. С чем-то не согласен, по каким-то вопросам имею мнение и высказываюсь, не более того. Вот насчет того что про милиционеров — философия не согласен. Потому что я сам хоть и умею пользоваться например GF или еще какой мудреной штукой, но при разработке я принимаю решение использовать ли её или нет чтобы быть увереным в дальнейшем что какой-то код делает нечто определенное и не более того, с какими-то гарантиями. Это происходит из осознания своих собственных ограничений, в голове может умещаться не больше какого-то предела. Так что ограничение сложности нужно не только для недостаточно компетентных работников, но и для наиболее эффективной работы вообще. Если такое ограничение сложности требует значительных затрат времени или изучения новых понятий это плохо.

Еще раз на счет self: я согласен, это действительно «пальцем в небо». Данное решение — пожалуй, самый правильный вариант в условиях наличия искуственного ограничения: принадлежности методов классам.

К слову, можно ли GF в Лиспе расширять замыканиями? Т.е. например синтезировать новые методы и предикаты из данных пользователя во время исполнения.

В олбщем, в Lisp’е все то же, что делается во время компиляции, можно делать и во время исполнения (механизм GF, во всяком случае, полностью динамичен). Однако, я не совсем понял, что Вы подразумеваете под «расширением GF замыканиями»

Наверное опечатка и имелось в виду «не важно»? Так что за уникальность тогда? Словарь Ожегова: «Уникальный — Единственный в своем роде, неповторимый».Пример с синтаксисом считаю корректным т.к. речь не о преимуществах, а именно о уникальности.

Сергей, Вы так увлеклись отрицанием всего, что я говорю, что даже придумали мне опечатку. Опечатки нет, я именно это и имел в виду.:)

Иногда нужна просто функция. Иногда нужен просто метод.

В Lisp’е все это есть, да и вы сами это, вроде как, должны знать (если писали о том, что нельзя превратить обычную функцию в generic).

Делая его неявным (зачем???) мы сразу делаем невозможным ряд манипуляций (например декораторы сразу идут лесом)... Причем это как раз пример не абстракции принесенной в жертву, а ровно наоборот. В этой проекции функция и метод — одно и то же.

Совершенно согласен. Но проблема дублирования информации при определении метода остается.На счет Микелянджело, милиции и чайников — это уже чистая философия и спорить тут бесполезно. Мое мнение такое: программисты — не милиция, а knowledge workers, которые на своем уровне должны иметь возможность решать, что правильно, а что нет. Это решение нельзя аутсорсить наверх, точнее можно, но только в краткосрочной перспективе оно будет работать. Но тут мы с вами, видимо, никогда не согласимся.

А хорошо ли PEAK-Rules известны даже в Python среде?

Малоизвестны. Вообще библиотека довольно свежая, но причина скорей в том что GF нужны не очень часто и еще реже являются центральным элементом программы. Разных реализаций GF для Python уйма, есть и новые и старые, тот же RuleDispatch, предшественник PEAK-Rules. Очень часто вместо применения GF используется локальное специализированное решение меньшей мощности, я догадываюсь что это кажется неверным, но это зря, т.к. точно также наверное кажется излишним специализированый синтаксис вместо горы скобочек. Эта неизвестность конечно имеет минусы, но такой баланс я всё равно считаю лучшим чем имеющийся в Лиспе.К слову, можно ли GF в Лиспе расширять замыканиями? Т.е. например синтезировать новые методы и предикаты из данных пользователя и добавлять их к GF во время исполнения?

Я говорю о технологиях, которые являются в своем роде лучшим решением, и важно, есть они в других языках или нет.

Наверное опечатка и имелось в виду «не важно»? Так что за уникальность тогда? Словарь Ожегова: «Уникальный — Единственный в своем роде, неповторимый».Пример с синтаксисом считаю корректным т.к. речь не о преимуществах, а именно о уникальности.

Как раз в случае с PEAK-Rules, я так понимаю, дело обстоит иначе — это как раз больше из категории расширений.

Извините, но нет. Это из категории библиотек.

А в чем, кстати, опасность CLOS-подобной множественной диспетчиризации?

В спагетти коде и ненужной сложности понимания реализации и отладки. Подобное кстати можно сказать насчет ООП-фанатизма в Java. Иногда нужна просто функция. Иногда нужен просто метод. Кому-то покажется что того, что они — частный случай GF достаточно для того чтобы иметь в языке только GF, но это математический, а не инжинерный подход и потому в применении к программированию неверный.

В LISP же получается либо всё либо ничего.

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

Реализовать можно, но это как раз препятствие к упрощению, а не к усложнению, о чем я и говорил. Это знаете как в анекдоте когда математик предлагал вылить воду из чайника сводя задачу к предыдущей. И вывод вполне логичен, т.к. для использования ООП в Лиспе необходимо изучить CLOS, т.е. кривая обучения ступенчастая — всё или ничего.

Таким образом, если не хотите стрелять по воробьям из пушки, ограничте себя сами — у вас есть такая возможность, но ни BDFL, ни Microsoft вас не будет ограничивать.

Тут целый ряд возражений. Вопрос не в том чтобы иметь возможность себя ограничить. А в том чтобы сложность разработки была пропорциональна сложности задачи. Это решается путем предоставления инструментов разной сложности и мощности. Говоря о том что можно ограничить себя самого вы несколько лукавите, это как Микеланджело говорил что для того чтобы создать скульптуру достаточно отсечь от камня всё лишнее. Вроде и верно, но неправда.Далее, говорят о том что их «ограничивает BDFL» как правило самовлюбленные идиоты считающие что знают лучше всех. Не слушайте их. Им бы смотреть на решения более умных и опытных людей и пытаться понять почему так лучше. Им же конечно мешают чужие решения.Да и вообще вся эта идея «лучше сразу максимум, ограничивать потом» сродни тому чтобы раздавать патрульным милиционерам гранаты, и пусть сами решают в кого попадут осколки.

И, к тому же, говорить, например, что параметр self должен быть, потому что иначе некоторые общие вещи не будут работать — вот тут и вылазит боком дизайн, основанный на жертве абстракции конкретным сиюминутным потребностям.

Увы, тут вы попали пальцем в небо. Параметр self есть потому что он есть в любом случае. В любом ОО языке. Делая его неявным (зачем???) мы сразу делаем невозможным ряд манипуляций (например декораторы сразу идут лесом), ухудшаем читаемость кода итд итп. Причем это как раз пример не абстракции принесенной в жертву, а ровно наоборот. В этой проекции функция и метод — одно и то же. Есть огромная практическая польза от того чтобы этот факт был явно выражен в языке. В то же время от обобщения методов до GF больше вреда чем пользы, поэтому возможность «апгрейдить» их в отдельных случаях являет куда лучшим решением. Отдельно хотелось бы услышать о «вот тут и вылазит боком дизайн». Каким таким боком вылазит self, позвольте поинтересоваться?

Ну и, напоследок, обратите внимание: я не говорил о том, что Lisp является уникальным языком.

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

2 Сергей ЩетининНа счет незнания:, а хорошо ли PEAK-Rules известны даже в Python среде? Какие библиотеки/программы их используют? Я не Python-программист, поэтому мне трудно судить о том, насколько они хорошо вписываются в язык. Когда мы увидим use-cases (хотя бы такие, которые я привел для CLOS/MOP), можно будет оценить насколько более широкие возможности они дают. А пока что это просто слова, которые также частично основанны на Вашем незнании.P.S. На самом деле мы все много чего не знаем

2 Сергей Щетинин:

Я кстати так и знал что разговор придет к Тюринг-полноте, но ведь нельзя говорить что C уникален потому что он первым использовал «C-подобный синтаксис»? Точно также, не отрицая заслуг LISP, нельзя говорить что те или иные его возможности уникальны только потому что они были уникальны 30 лет назад.

Тут Вы, что называется, путаете божий дар с яичницей. Синтаксис С является скорее всего ad-hoc решением, которое имеет сомнительное преимущество перед, скажем, синтаксисом Fortran или Algol, которые существовали в то время. Я говорю о технологиях, которые являются в своем роде лучшим решением, и важно, есть они в других языках или нет. А про Тьюринга я вспомнил, чтобы указать на разницу между библиотекой и расширением языка. На С тоже можно написать MOP и CLOS, но это не будет расширением языка, поэтому полноценное использование такой системы будет невозможно. Как раз в случае с PEAK-Rules, я так понимаю, дело обстоит иначе — это как раз больше из категории расширений.

Т.е. LISP тут дает дает пушку для стрельбы по воробьям (а стрелять чаще всего надо по ним), никакого менее мощного и опасного инструмента вообще не предлагается.

А в чем, кстати, опасность CLOS-подобной множественной диспетчиризации?;)

... Это вполне в стиле LISP, но это его минус... В LISP же получается либо всё либо ничего.

Этот вывод совершенно нелогичен, хотя и часто встречается. Но удивительно его слышать от людей, которые как раз занимаются концептуальными вещами. Имея MOP, не представляет трудности реализовать диспетчиризацию только по первому аргументу и принадлежность методов объектам в качестве расширения языка, использовать ее самому и дать другим. А вот пойти от обратного несколько труднее. Вы правы в том, что путь Лиспа — дать программисту наибольшие возможности в области абстракции, а не приводить все к общему знаменателю «среднего разработчика». Потому что разделяются уровень языка и уровень библиотек. Имея гибкий язык, мы реализовываем на нем уровень общих библиотек (которые просто расширяют, дополняют или сужают его по необходимости) — это может быть CLOS, а может быть «стандартное OOP», — и они могут использоваться параллельно. Таким образом, если не хотите стрелять по воробьям из пушки, ограничте себя сами — у вас есть такая возможность, но ни BDFL, ни Microsoft вас не будет ограничивать. И, к тому же, говорить, например, что параметр self должен быть, потому что иначе некоторые общие вещи не будут работать — вот тут и вылазит боком дизайн, основанный на жертве абстракции конкретным сиюминутным потребностям.Так что «все или ничего» здесь ни при чем.Ну и, напоследок, обратите внимание: я не говорил о том, что Lisp является уникальным языком. По-своему уникальны Python, Haskell, SmallTalk, Forth... И как раз Python, к примеру, является действительно высокоуровневым языком. Но я говорил о тех технологиях, которые в Lisp’е достигли определенной степени совершенства и на порядок развитие, чем в мейнстриме.

Про отличия.Насколько я вижу обычную функцию в LISP нельзя превратить в generic. В P-R можно.Не смог разобраться насколько полно переопределяема в CLOS логика переопределения и комбинации. Например можно ли было сделать call-next-method с нуля? Можно ли сделать аналог before, но с запитыванием его результата в дальнейшую цепочку исполнения тем или иным способом? Например умножить на него результат основного метода; аналогично для их цепочки? Требуется ли для этого содействие остальных типов методов (before / after /...)? В P-R все эти части достаточно высокоуровневые и не являются базисом системы.Публикацию «Predicate Dispatching in the Common Lisp Object System» пролистал. Действительно похоже на реализацию в P-R, но насколько я могу судить там реализовано достаточно малое подмножество того что умеет P-R, во всяком случае я так понял что используется конъюнкция предикатов, никаких деревьев с произвольными узлами. Также не заметил ничего приближающегося к pattern-matching и упорядочивания pattern’ов. Например то что (arg < 0) перегружает (arg < 100), а (0 < arg < 3) и (1 < arg < 4) для arg == 2 конфликтуют.В общем я надеюсь мы уже уяснили что говорить про уникальность технологий Лиспа можно только по незнанию.

Конечно PEAK-Rules был построен с учетом уроков CLOS и потому разумно повторил его везде где было уместно. Это нужно хотя бы для того чтобы программистам уже знакомым с CLOS не нужно было переучиваться. Я кстати так и знал что разговор придет к Тюринг-полноте, но ведь нельзя говорить что C уникален потому что он первым использовал «C-подобный синтаксис»? Точно также, не отрицая заслуг LISP, нельзя говорить что те или иные его возможности уникальны только потому что они были уникальны 30 лет назад.Насчет того что PEAK-Rules не является частью языка, то я считаю это победой Python. С одной стороны это демонстрирует что то, что в ином языке требует расширения языка, в Python реализуется библиотекой. С другой стороны в Python, как и в любом другом языке с поддержкой ОО есть диспатчинг по типу первого аргумента (а также по его is-a), для минимальных усложнений диспечеризации есть удобное соглашение (опять обратите внимание на легковесность решения) которого обычно хватает (см. radd итп). То что для перехода к CLOS-подобной диспечеризации требуется некоторый скачек это несомненный плюс. Во-первых это усложнение реально требуется не слишком часто, не будь тут никакого барьера код превратился бы в кашу просто потому что слишком многие хотят всё делать как можно мудрее, а не как можно надежнее и проще. Т.е. LISP тут дает дает пушку для стрельбы по воробьям (а стрелять чаще всего надо по ним), никакого менее мощного и опасного инструмента вообще не предлагается. Это вполне в стиле LISP, но это его минус.PEAK-Rules действительно никогда не станет мейнстримом, но это не должно мешать Python быть мейнстримом. В LISP же получается либо всё либо ничего.Изучу ваши ссылки более внимательно и отвечу про отличия / преимущества.

2 Сергей Щетинин: Method Combination: http://www.cs.cmu.edu/Groups/A....В том числе: Declarative Method Combination (способы комбинации, определенные пользователем) Что касается диспетчиризации — все правильно, стандартныей CLOS действительно диспетчиризирует только по указанным двум предикатам. Есть, конечно, и решения по расширению до диспетчиризации по произвольным предикатам с помощью, каконечно же, MOP, например: http://dspace.mit.edu/bitstrea.... Думаю, те, кому не хватает 2 стандартных вариантов, используют нечто подобное....Все-таки не могу отделаться от мысли, что концептуально PEAK-Rules полностью повторяет CLOS — еще одно подтверждение, что CLOS близок к тому, чтобы быть «локальным максимумом» в этой области. Разумеется, «уникальность» из названия статьи в некотором роде относительна. В конце концов, в любом Тюринг-полном языке, конечно, можно создать что-то подобное и даже больше. Уникальность CLOS заключается в том, что это была первая полноценная и в то же время достаточно полная реализация этих концепций, которую повторяют (возможно, с некоторыми дополнениями) другие. А также в том, как она интегрированна в язык и, поэтому, используется большинством программистов на Lisp, в то время как в других языках это пока не является мейнстримом. Такая же система в виде библиотеки может никогда не стать полноценной частью языковой среды. (Думаю, это не относится к PEAK-Rules, но хорошим примером тут могут быть C++ boost библиотеки, которые фактически не признаются очень многими разработчиками [например, Google практически подностю запрещает их использование]. Наверное, потому что для них это уже не С++...).Дальше эта тема развивается здесь: http://bc.tech.coop/blog/04040....

@Всеволод Дёмкин

поскольку с Python я знаком лишь теоретически, то хотелось бы, если можно получить более детальный комментарий...В описании PEAK-rules я пока вижу лишь полное повторение CLOS — может я чего-то не заметил?

Я в свою очередь не пишу на LISP и CLOS не использовал, только читал о. Поэтому могут быть ошибки в сравнении. Насколько я понимаю у CLOS диспатчинг идет по отношениям instance-of и is-a, в PEAK-Rules на данный момент реализованы два движка диспатчинга, второй из них умеет проверять произвольные условия для аргументов, и не смотря на это для разных перегруженных функций определяются конфликты / полная перегрузка, оптимальный порядок проверок и т.д. Кроме того можно определять новые типы методов (помимо стандартных before / when / after / around) для которых можно реализовывать произвольные алгоритмы перегрузок или комбинаций (интересно услышать о том как работает поддержка подобного в LISP, я встречал упоминания что она есть). Также можно реализовать новые движки диспатчинга, инфраструктурой это предусмотрено.Расширение самой библиотеки PEAK-Rules и её движков также реализуется за счет расширения-переопределения её функций (например implies, overrides итд).Подробней можно прочитать в .txt файлах в репозитории.

А что вы скажете о мета-объектном протоколе: можно ли в Python выбрать то или иное представление класса (чтобы реализовать, например, что-то подобное ContextL) и как это сделать?

К сожалению сейчас нет времени вникать в ContextL в достаточной мере чтобы ответить наверняка, но судя по тому с чем успел ознакомиться — да, всё это можно реализовать и в какой-то мере реализовано. Вообще я как раз последнее время занимался темой «контекстно-ориентированного» программирования и некоторые решения повторяют описанное, но в целом я тяготею в сторону более явных подходов. На эту тему можно посмотреть например Contextual или мою вариацию на ту же тему (на данный момент несколько устаревшую).Опять таки, задачи, которые я встретил в описаниях, определенно решаются в Python достаточно элегантно.

2 Всеволод Демкин. Спасибо за детяльное объяснение. О некоторых вещах узнал впервые, о макросах чтения, книге Let over lambda.

2 motus: сейчас занимаюсь разработкой массового веб-сервиса. А в планах: язык структуризации информации и векторный редактор для рисования эскизов (ну, планов, конечно, громадьё:)

2 aleksijb: неправильно я сказал на счет доступа к динамическому окружению — все будет работать.

классно! особое спасибо за ссылку на «Let over Lambda»., а расскажи, пожалуйста, что ты с лиспом делаешь?

Не могу сейчас ничего найти для ответа на этот вопрос, но C#-повики меня уверяли, что такое вводится (может, будет в 4-й версии?)

Да, вот я тоже ничего не нагуглил. Обычно о новинках в свежей версии C# можно прочитать в блогах его разработчиков.

2 Сергей Щетинин: поскольку с Python я знаком лишь теоретически, то хотелось бы, если можно получить более детальный комментарий...В описании PEAK-rules я пока вижу лишь полное повторение CLOS — может я чего-то не заметил? А что вы скажете о мета-объектном протоколе: можно ли в Python выбрать то или иное представление класса (чтобы реализовать, например, что-то подобное ContextL) и как это сделать?

2 kmmbvnr: Привет, Миша! Не могу сейчас ничего найти для ответа на этот вопрос, но C#-повики меня уверяли, что такое вводится (может, будет в 4-й версии?)

2 alekijb: На счет AIF: В CL весь контекст по-умолчанию лексический, динамический контекст вводится специальными формами, которых не много. Так что для Lisp-программиста проблем с распознаванием контекста в данном случае не возникнет. Более того, в книге «Let over Lambda» (которую я приводил в ссылках) обсуждается значение, которое имеет в CL дуализм (в том числе и дуализм контекста) —, но это отдельная долгая дискуссия.А вот что касается дырявой абстракции — это как раз пример ее разумного использования. Дырка тут в другом: в том, что переменная IT захватывается макросом, чего в общем случае делать нежелательно (об этом есть целая глава в «On Lisp»), но в локальных границах одной понятной формы это дает ощутимый выигрыш.Что же касается варианта реализации этой конструкции на замыканиях, то, во-первых, мы потеряем доступ к динамическому контексту (точнее, получим его состояние на момент чтения этой формы, а не ее исполнения). Во-вторых, как по мне, то в таком варианте мы получим второсортную синтаксическую конструкцию. В том то и удобство использования макросов, что они дают возможность полностью убрать повторяющийся синтаксис (даже, если бы lambda-форма привносила не 8 лишних символов, а 1, его бы все равно пришлось повторять и, самое главное, разделять в уме встроенные операторы языка и определенные пользователем функции). Кроме того, мы избавляемся от лишнего вызова функции...Кстати, с lambd’ой больших проблем нет: * можно опять же сделать макрос, и будет вместо lambda — fn:

(defmacro fn ((&rest args) &body body)
(lambda (,@args) ,@body))</code></pre><br>* я использую такой reader-macro для lambd с одной переменной (смоделированной по Arc'овскому варианту Пола Грехэма):<br><pre><code>; a PaulGraham'ish reader syntax for one argument lambdas. Example: #((1+ ) (print )) -> (lambda (_) (1+ ) (print )) (set-dispatch-macro-character ## #\<br>   (lambda (stream subchar arg)<br>     (declare (ignore subchar)<br>      (ignore arg))<br>     (let ((sexp (read stream t nil t))<br>   (x (gensym)))<br>(lambda (,x) ,@(subst x ’_ (if (listp (car sexp)) sexp (list sexp)))))))

На счет доступа к стеку в SmallTalk: я согласен. В этом отношении динамизм SmallTalk’а непревзойден.

«Система обработки ошибок / сигнальный протокол»... (разве что в C# сейчас вводится нечто подобное)...

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

В машинных кодах и то легче понять программу чем в Lisp; -)

Про уникальность LISP заявление излишне смелое. Например PEAK-Rules является надмножеством CLOS.

Интересная статья. Есть некоторые вопросы по поводу макросов.В приведенном выше примере при вызове макроса aif, передается выражение (cdr it). Внешне использование it выглядит как динкамический контекст (то есть когда байдинг определения и использования переменной определяется из рантайм окружения, в отличие от лексического контекста когда байдинг можно отследить по структуре кода). После раскрытия макроса все станет на свои места и it получит свой let. То есть получаем дырявую абстракцию aif, для использования которой необходимо знать внутренности реализации.Почему не реализовать aif на обычных замыканиях?

(define aif (it then else)  (if it       (then it)       (else)))

Тогда вместо (car it) была бы лямбда (lambda (it) (car it))? Единственное объяснение которое мне приходит в голову — lambda запись в лиспе слишком громоздкая. Интересно узнать другие соображения. С другой стороны лямбды так часто используются в лиспе что неплохо было бы меть для них более короткую запись поддерживаемою, например, редактором. Тогда и другие управляющие структуры можно было бы реализовывать на замыканиях.

А где можно почитать как в С# вводится система обработки исключений подобная Lisp?

Отличная статья, немножко скомкано, но суть передана верно.Тема с развертыванием макросов в lisp-код на этапе компиляции тоже мало затронута, хотя одна из самых вкусных.Но в любом случае, спасибо, что пишете подобные статьи.

2 Віктор: дякую за відгук2 Denix: Из open-source реализаций основными являются: * SBCL (платформа: Linux; для Windows реализация пока что не полна — например, не поддерживаются threads) * GNU CLisp (пожалуй, единственный интерпретатор; есть на всех платформах) * Clozure CL (бывший Macintosh CL: буквально недавно объявлена поддержка Windows. Ну а, Mac и Linux — понятно. В отличие от 2-х других, не пользовался, но отзывы о нем очень хорошие) Все 3 вполне подойдут для начала. Вопрос только в вашей ОС.Ну и вам понадобится IDE: 2 самых распространенных сейчас варианта — это Emacs + SLIME (режим для работы с CL) и Eclipse + CUSP plugin.Есть есть Lisp из коробки (http://common-lisp.net/project.../), основанный на Емакс. Года 2 назад я без проблем его установил. Однако, после того не следил за проектом, так что YMMV.

Чудова стаття. Сам нещодавно став вивчати Коммон Лісп і був вражений його потужністю, гнучкістю, швидкістю реалізацій та зручністю у використанні.

Подписаться на комментарии