Уникальные технологии Common Lisp (с примерами использования)Источник: developers Всеволод Дёмкин
Базовые подсистемы языкаВ языке Common Lisp есть как минимум 3 инфраструктурных технологии, во многом формирующие подходы к его применению, которые в других языках либо отсутствуют вовсе, либо реализованы в очень ограниченном варианте. Для компенсации их отсутствия пользователи других языков часто вынуждены использовать Шаблоны проектирования, а порой и вообще не имеют возможности применять некоторые более эффективные подходы к решению типичных задач. Что это за технологии и какие возможности дает их использование? Макросистема
Примеры применения:
Мета-объектный протокол и CLOS
Примерами использования мета-объектного протокола также являются инфраструктурные системы языка, реализованные в виде библиотек:
Библиотека CLSQL создана для унификации работы с различными SQL базами данных. Кстати, на ее примере можно увидеть проявление мультипарадигменности Common Lisp: у библиотеки есть как объектно-ориентированный интерфейс (ORM), реализованный на основе CLOS, так и функциональный (на основе функций и макросов чтения). С помощью мета-объектного протокола стандартный класс языка расширяется специальным параметром - ссылкой на таблицу БД, к которой он привязан, а описания его полей (в терминологии Lisp: слотов) - дополнительными опциональными параметрами, такими как: ограничение уникальности, ключа, функция-преобразователь при записи и извлечении значения из БД и т.д. Система обработки ошибок / сигнальный протоколСистема обработки ошибок есть в любом современном языке, однако в CL она все еще остается в определенном смысле уникальной (разве что в C# сейчас вводится нечто подобное). Преимущество этой системы заключается опять же в ее большей абстрактности: хотя основная ее задача - обработка ошибок, точнее исключительных ситуаций,- она построена на более общей концепции передачи управления потоком выполнения программы по стеку. …Как и системы в других языках. Но в других языках есть единственный предопределенный вариант передачи управления: после возникновения исключительной ситуации стек отматывается вплоть до уровня, где находится ее обработчик (или до верхнего уровня). В CL же стек не отматывается сразу, а сперва ищется соответствующий обработчик (причем это может делаться как в динамическом, так и в лексическом окружении), а затем обработчик выполняется на том уровне, где это определенно программистом. Таким образом, исключительные ситуации не несут безусловно катастрофических последствий для текущего состояния выполнения программы, т.е. с их помощью можно реализовать различные виды нелокальной передачи управления (а это приводит к сопроцедурам и т.п.) Хорошие примеры использования сигнального протокола приведены в книге Practical Common Lisp (см. ниже). Вспомогательные технологии Кроме того в CL есть ряд технологий менее значительных, которые нельзя назвать в полной мере уникальными, но которые существенно упрощают его применение и делают программы более ясными, а также дают дополнительные возможности для расширения языка: Протокол множественных возвращаемых значенийДает возможность возвращать из функции несколько значений и по желанию принимать все их (и привязывать к каким-то переменным) или только часть. По-умолчанию для кода, не использующего эту функциональность, передается только 1-е значение. Казалось бы, это простая возможность, однако, на поверку, она требует обширной поддержки на языковом уровне (учитывая необходимость поддержки возврата из блоков и т.п.). Протокол обобщенных переменныхЭто аналог свойств в некоторых ОО-языках. Концептуально, оперирует понятием места (place) - по сути дела ячейки памяти, однако не физической (без манипуляции указателями) - это может быть просто объект или же элемент какой-то структуры (будь-то опять же объект, список, массив и т.д.) Таким образом, имеются намного большие возможности, чем при использовании обычных свойств, поскольку для любой функции, которая читает значения какого-либо места, можно указать функцию которая его значение задает. Макросы чтенияЭто инструмент модификации синтаксиса языка за пределы s-выражений, который дает программисту возможность, используя компилятор Lisp, создать свой собственный синтаксис. Его работа основана на фундаментальном принципе Lisp-систем: разделении времени чтения, времени компиляции и времени выполнения - REPL (Read-Eval-Print Loop). Обычные макросы вычисляются (раскрываются, expand) во время компиляции, и полученный код компилируется вместе с написанным вручную. А вот макросы чтения выполняются еще на этапе обработки программы парсером при обнаружении специальных символов (dispatch characters). Механизм макросов чтения является возможностью получить прямой доступ к Reader"у и влиять на то, как он формирует абстрактное синтаксическое дерево из "сырого" программного кода. Таким образом, можно на поверхности Lisp использовать любой синтаксис, вплоть до, например, C-подобного. Впрочем, Lisp-программисты предпочитают все-таки префиксный унифицированный синтаксис со скобками, а Reader-макросы используют для специфических задач. Пример такого использования - буквальный синтаксис для чтения hash-таблиц, который почему-то отсутствует в спецификации языка. Это, кстати, еще один пример того, каким образом CL дает возможность изменить себя и использовать новые базовые синтаксические конструкции наравне с определенными в стандарте. Основывается на буквальном синтаксисе для ассоциативных списков (ALIST):
ПослесловиеВ заключение хотелось бы коснуться понятия высокоуровневого языка программирования. Оно, конечно, является философским, поэтому выскажу свое мнение на этот счет: по-настоящему высокоуровневый язык должен давать программисту возможность выражать свои мысли, концепции и модели в программном коде напрямую, а не через другие концепции, если только те не являются более общими. Это значит, например, что высокоуровневый язык должен позволять напрямую оперировать такой сущностью, как функция, а не требовать для этого задействовать другие сущности такого же уровня абстракции, скажем, классы. Подход к созданию высокоуровневого языка можно увидеть на примере Common Lisp, в котором для каждой задачи выбирается подходящая концепция, будь то объект, сигнал или место. А что дает нам использование по-настоящему высокоуровневых языков? Большую расширяемость, краткость и адаптируемость программы к изменениям, и, в конце концов, настоящую свободу при программировании! |