Что значит код как данные
Про Лисп говорят, что код это данные, а данные – код. Пока не поработаешь с Лиспом, понять смысл кода как данных трудно. Ниже – моя попытка объяснить простыми словами.
Предположим, кто-то осваивает Лисп и делает задачки вроде факториала и
рекурсии. Частенько приходится записывать числовые выражения вроде
(x + y) * (a / b)
. В Лиспе пишут польской нотацией, то есть
выражение выглядит так:
(* (+ x y)
(/ a b))
Не очень привычно. Было бы здорово, думает студент, написать функцию,
которая принимает выражение как удобно мне, а возвращала то, что нужно
Лиспу. Начинает писать функцию expr
, что-то вроде (expr (x + y) *
(a / b))
.
Ничто не предвещает беды. Но вот облом: Лисп, как и другие ЯП,
вычисляет аргументы функции до входа в нее. А выражение (x + y)...
ошибочно с точки зрения и семантики, и синтаксиса, поэтому даже до
вызова функции expr
дело не дойдет. Что же делать?
Помогут макросы.
Макрос похож на функцию, но с важным отличием. Выражение, переданное в макрос, НЕ вычисляется. Вместо этого макрос получает список лексем. Задача макроса – перестоить список в правильную Лисп-форму и вернуть ее, НЕ вычисляя. Интерпретатор сам вычислит ее, получив из макроса.
Короткими словами, макросу можно скормить полную дичь: крестики-нолики, математическое выражение, кусок кода на любом языке, asii-арт. Внутри макроса это станет списком. Задача макроса – перестроить дичь в правильный список, который вычислит Лисп.
Именно в этот момент срабатывает срабатывает принцип
код-как-данные. Когда мы пишем (expr (x + y) * (a / b))
, для нас это
код. Но внутри макроса это список! Нулевой элемент списка –
открывающая скобочка, первый – символ x, затем символ плюсика, и так
до последней закрывающей скобочки.
Рассмотрим макрос для вычисления выражений из двух операндов. Он
вычислит простейшие вещи вроде 3 + 2
, 2 * 10
, -3 / 2
:
(defmacro expr [v1 op v2]
`(~op ~v1 ~v2))
(expr 2 + 2)
>>> 4
(expr 2 / 2)
>>> 1
Видно что в макросе expr
я поменял элементы списка
местами. Выражения длиней трех лексем макрос не примет. Для
проивольного выражения понадобятся разбор и рекурсивный спуск, что
выходит за рамки разговора.
Вот почему Лисп – программируемый язык программирования. Любой участок кода может быть обработан как список, что делает язык невероятно мощным. Лисп разрешает любой недостаток своими же средствами. ООП-системы, средства обработки исключений и многие другие фундаментальные вещи – всего лишь пакеты с макросами.
PS: Кроме Лиспа, я знаю только один язык со схожим поведенем. Это Tcl (Тикль) – в котором, грубо говоря, все является строкой. Тикль до сих пор популярен на производстве: заводах, CAD-системах. Нефтяные платформы Shell работают под управлением системы, написанной на Тикле.
Нашли ошибку? Выделите мышкой и нажмите Ctrl/⌘+Enter
Marat Amerov, 23rd Oct 2020, link
А можно ли отнести аннотации в пыхе или жаве к "код как данные" ?
Ivan Grishaev, 24th Oct 2020, link , parent
Нет, потому что аннотации -- это не данные, а инструкции для препроцессора.
еламан, 20th Nov 2023, link
пар