Синтаксис Лиспа
Когда неподготовленный человек видит Лисп, он как-то реагирует: хихикает, лепит эмодзи, вовлекает других, словом — переживает. В такую минуту он напоминает школьника, который принес эротический журнал: смотрит на груди и попы, конфузится, краснеет, показывает другим под партой. Вроде бы интересно, но что с этим делать — не понятно.
Хорошо, если бы программистам объяснили: Лисп — лучший способ записать код. Любой язык можно улучшить хотя бы тем, что сделать синтаксис лиспо-подобным. Пусть даже парадигма останется прежней.
У скобочной записи есть преимущество: каждое выражение имеет начало и конец. Убедитесь, что прочли последнее предложение вдумчиво. В Лиспе каждая форма имеет начало и конец. В других языках — нет.
Предположим, я вижу выражение:
x = foo + bar
Значит ли это, что выражение закончено? Конечно нет. За bar
вполне может быть
продолжение:
x = foo + bar * kek + lol
Кроме того, что выражение определяется “на глазок”, сюда вкрадывается приоритет
операторов: bar * kek
нельзя разорвать.
В то время на Лиспе первое выражение будет таким:
(var x (+ foo bar))
Скобки задают границы. Если скобка закрылась, то выражение закончилось, точка. Все, что следует дальше, относится ко внешнему выражению. Второе выражение сводит на нет котовасию с приоритетом операторов:
(var x (+ foo (* bar kek) lol))
Все задано явно, вопросов быть не может. Вы, конечно, скажете, что приоритет умножения известен каждому школьнику? Тогда счастливой отладки дебажить что-то такое:
Some shit = foo && bar || test ^ foo;
Из сказанного следует, что в Лиспе удобно работать с выражениями. Например, я могу выделить текущую форму. Обратите внимание — форму! Не метод, не сложение чисел, не класс, а именно форму! Потому что в Лиспе все это — форма. Мне не нужны хоткеи “Select method”, “Select class”, “Select whatever”. Мне достаточно одной клавиши, чтобы покрыть все случаи.
Формы в Лиспе можно разбивать и объединять. Стоит нажать кнопку, и выражение (+
foo bar)
становится просто + foo bar
. Далее я могу что-то сделать с его
элементами. Форму можно двигать выше, ниже по текущему уровню вложенности. Можно
втолкнуть ее внутрь. Можно вытолкнуть наверх из-под условия.
Форма может поглощать другие формы. Например, у меня есть код:
(do-some-stuff x y z)
Теперь нужно, чтобы форма была внутри условия. Прямо над ней я пишу:
(when some-condition)
(do-some-stuff x y z)
Далее, находясь внутри when
, я жму кнопку, и форма втягивает в себя следующую
за ней форму, и получается:
(when some-condition
(do-some-stuff x y z))
Разумеется, есть другая кнопка, чтобы “выплюнуть” форму, и я получу то, что было до поглощения.
Каждый думает, что к нему это не относится, ведь он же пишет не на Лиспе. Но вот реальный пример на Джаве с цепочкой футур:
return prepare(sql, executeParams)
.thenCompose((PreparedStatement stmt) -> sendBind(portal, stmt, executeParams))
.thenCompose((Integer ignored) -> sendDescribePortal(portal))
.thenCompose((Integer ignored) -> sendExecute(portal, executeParams.maxRows()))
.thenCompose((Integer ignored) -> sendClosePortal(portal))
.thenCompose((Integer ignored) -> sendCloseStatement(stmt))
.thenCompose((Integer ignored) -> sendSync())
.thenCompose((Integer ignored) -> sendFlush())
.thenCompose((Integer ignored) -> interact(executeParams))
.thenCompose((Result res) -> CompletableFuture.completedFuture(res.getResult()));
Каждый видит в меру своей испорченности, но я вижу здесь Лисп. Ему немного не повезло: нужно только переставить скобки, и получится нормально. Но вот курьез: Джава-человек в упор этого не видит. Для него это по-прежнему код на Джаве, а переставишь скобки — и сразу смешно.
Теперь нужно изменить код так, чтобы stmt
оставался в поле видимости на
большее число шагов. Получится вот так:
return prepare(sql, executeParams)
.thenCompose((PreparedStatement stmt) ->
sendBind(portal, stmt, executeParams)
.thenCompose((Integer ignored) -> sendDescribePortal(portal))
.thenCompose((Integer ignored) -> sendExecute(portal, executeParams.maxRows()))
.thenCompose((Integer ignored) -> sendClosePortal(portal))
.thenCompose((Integer ignored) -> sendCloseStatement(stmt)))
.thenCompose((Integer ignored) -> sendSync())
.thenCompose((Integer ignored) -> sendFlush())
.thenCompose((Integer ignored) -> interact(executeParams))
.thenCompose((Result res) -> CompletableFuture.completedFuture(res.getResult()));
Знали бы вы, как тяжело это было сделать! Поскольку у нас не формы, а выражения, приходится выделять каждое мышкой, вырезать и копировать. Ошибся на одну скобку — и все, начинается ад. Окончание каждого метода неочевидно. Над таким примитивным рефакторингом я сидел полчаса. В Лиспе я бы подвинул формы, даже не отдав себе отчета в том, что делаю.
Что тут можно сказать? Есть вещи, которые хоть и были открыты давно, остаются непревзойденными. Синтаксис Лиспа — одна их них. Много воды утекло с 1958 года, но в плане работы с кодом удобней ничего не придумали.
Не обязательно писать на Лиспе, но нужно знать эту его сторону. Чтобы не выглядеть глупо, не хихикать и не прыскать в кулачок, когда случится увидеть Лисп.
Нашли ошибку? Выделите мышкой и нажмите Ctrl/⌘+Enter