Когда неподготовленный человек видит Лисп, он как-то реагирует: хихикает, лепит эмодзи, вовлекает других, словом — переживает. В такую минуту он напоминает школьника, который принес эротический журнал: смотрит на груди и попы, конфузится, краснеет, показывает другим под партой. Вроде бы интересно, но что с этим делать — не понятно.

Хорошо, если бы программистам объяснили: Лисп — лучший способ записать код. Любой язык можно улучшить хотя бы тем, что сделать синтаксис лиспо-подобным. Пусть даже парадигма останется прежней.

У скобочной записи есть преимущество: каждое выражение имеет начало и конец. Убедитесь, что прочли последнее предложение вдумчиво. В Лиспе каждая форма имеет начало и конец. В других языках — нет.

Предположим, я вижу выражение:

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 года, но в плане работы с кодом удобней ничего не придумали.

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