Pull to refresh

Comments 138

UFO just landed and posted this here
Один менеджер проекта жаловался, что его инженеры плохо воспринимают прототипы на Haskell из-за практически полного отсутствия там пунктуации…
UFO just landed and posted this here
В таком случае, пользуясь вашей же логикой — разумное количество спецсимволов — это ведь хорошо?
Если да, то вопрос уже в параметре частоты таких символов, и его оптимальное значение каждый будет оценивать субъективно.

На мой субъективный взгляд, кол-во спецсимволов в кложе вполне в рамках приличий. Уже не говоря о том, что в публичных АПИ они встречаются крайне редко. За исключением адового core.async, но там и задачи атипичные для 95% программистов.
UFO just landed and posted this here
Мне кажется, сравнивать PHP и Clojure на основе их синтаксисов — это как сравнивать девушек на основе первых букв имён их матерей. Да, синтаксис Clojure на первый взгляд выглядит немного странно (он носит сандали), но он не раскрывает основных особенностей языка — поддержка макросов и символьного программирования, прекрасная многопоточность с немутабельными коллекциями и STM, принцип единообразного интерфейса, лёгкое взаимодействие с кучей готовых библиотек на той же платформе и т.д.
Я иммутабельные данные, которые ещё и дают гарантии по времени исполнения — поставил бы вообще на первое место. Добавить элемент в начало списка размером в миллион элементов — за константа, добавить элемент в конец такой же длины массива — константа, получить новую версию коллекции, где пятый элемент заменён на вашу любимую структуру данных — константа. (На самом деле O(log32 n), но кого волнуют сверхмалые).

Далее идёт маркетинговый булщит и наркомания.
Для веб-программистов также стоит рассказать какое это удовольстие писать SQL-запросы под sql-korma. Представьте что вы пишете псевдо-SQL, а в дополнение вы можете составлять запросы как LEGO, включая or, and, in условия. То есть все эти условия можно передать свыше, как аргумент для функции которая в итоге делает селект.

Ещё для веб-программистов, отдельного слова заслуживает hiccup — самый царский способ генерить html. Представьте что Вы можете описать всю структуру страницы в виде данных — массивы, словари, списки. Вы просто генерите данные, и можете в любой момент отдать их какой-нибудь функции которая их пощупает и вернёт новую версию (не ломая старую версию). То есть генерация разных подтипов html-компонента превращается в хруст вафель. Вы работаете с данными, в языке который имеет сотни встроенных функций для работы с данными, который просто кромсает, уничтожает, генерит и месит данные, и вообще сам язык и есть данные — и это неописуемый восторг. Я фулл-стек программист, и clojure + hiccup — это лучший шаблонизатор что я пробовал из php, handlebars, jade. Ещё hiccup занимается там какой-то магией компиляции с макросами (хиккап и есть макрос), чтобы это ещё и быстро работало, но я разбираться не стал, и основные шаблоны просто обернул в мемоизацию.

А новая версия кложуры несёт ещё исключения с данными. То есть Вы сможете принимать решения исходя не только из типа исключения, но и используя дополнительные данные. Например, у Вас вылетело исключение, из-за неправильного значения параметра, а вам источник исключения, раз и прислал допустимый диапазон.

Удачно кто-то подметил: после того как ПРИВЫКАЕШЬ к иммутабельным данным, не можешь понять зачем тебе нужны были мутабельные.
Но должен признаться, поначалу бесился каждый раз делать присваивание, чтобы сохранить новую версию данных.
А новая версия кложуры несёт ещё исключения с данными. То есть Вы сможете принимать решения исходя не только из типа исключения, но и используя дополнительные данные.

Я так понимаю, что это просто специальный подкласс Exception, где есть Map дополнительных параметров? Так это и раньше можно было сделать.

Гораздо интересней посмотреть на conditions в Common Lisp. Conditions — это как исключения, только не ломающие стек, а просто на время передающие управление выше. Что делать с этим condition решает уже управляющий код — может остановить операцию, может повторить или что угодно ещё. Например, пишете вы функцию для пакетной обработки некоторых записей. Что делать, если одна запись оказалась невалидной? В зависимости от ситуации, вы можете захотеть пропустить её, залогировать или поломать весь вызов. Но это решает вызывающий код, на уровне своей функции вы это решить не можете. Conditions как раз и позволяет сообщить наверх про, собственно, сложившееся условие, и позволить управляющему коду самому решать, продолжать ли операцию.

В Clojure, кстати, я уже где-то видел реализацию этой фичи, но сходу не найду.

Удачно кто-то подметил: после того как ПРИВЫКАЕШЬ к иммутабельным данным, не можешь понять зачем тебе нужны были мутабельные.

Немутабельные данные действительно во многом покрывают область действия мутабельных, но как оказалось не всегда. Например, в задачах компьютерного зрения даже однократное копирование изображения в алгоритме может привести к значительному снижению производительности, а о об изменении пикселей по одному вообще не может идти речи.
Гораздо интересней посмотреть на conditions в Common Lisp.

Ок, надо подумать над этим. В любом случаею, в ex-info можно сунуть парочку колбеков. Для меня важно, что стандартные исключения — теперь выражены в форме данных.

Хех, Clojure же не копирует сами данные, при создании копии. А изображения тоже можно представить на стандартных структурах.
Но, конечно, у всего есть границы, и везде есть низкоуровневые компоненты.
UFO just landed and posted this here
В си подобных языках тоже много избыточных элементов синтаксиса.
Если вы попробуете написать несколько программ на Clojure, вы поймете, что к нему нужно просто привыкнуть, так же, как нужно привыкать в си-подобным языкам.

Некоторыми плюсами в работе с Clojure могут быть:
— Удобная, простая и быстрая работа со списками;
— Простая, понятная и легкая в разработке концепция паралельной обработки данных;
— Это таки функциональный язык, а не ООП с добавлением функциональщины.
Лично у меня складывается ощущение что автор языка создавал синтаксис не по принципу «как будет удобнее» а по принципу «как нет ещё ни у кого».


Это, я думаю, вы просто не очень интересуетесь языками программирования, раз не знаете про LISP.

Зачем? Какая тут реальная польза и удобство для программиста?


Прочитайте про макросы LISP — возможность создавать свои eDSL.

Процитирую классику

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

Главная фича скобочек — это то, что ты программируешь в чистом AST. Соответственно, ты так же легко можешь программно код прочитать и код сгенерить — и это и есть наиболее мощная разновидность метапрограммирования.
UFO just landed and posted this here
По поводу LISP — не только слышал о нём, но и изучал некоторое время.


Тогда зачем называть синтаксис «как ни у кого». Лисп создавался в 50е.

По поводу неважности синтаксиса — это выражение из той же серии что «главное — это душа».


Кстати, заметил, что при длительном общении с женщинами привыкаешь со временем к каким-то внешним особенностям и перестаешь их видеть.


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


Не «ужасное» а «непривычное».


Возможность создавать свои eDSL можно реализовать и в си-подобном синтаксисе, не так ли?


Есть, например, Nemerle. Но если синтаксис кода отличается от синтаксиса списка, то, если хочется использовать все функции накопленные для обращения со списками, применять постоянно какие-то преобразования.

Простым синтаксисом достигается унификация и уменьшение количества частных случаев, которых надо рассматривать.
Nemerle здох, но не в этом суть. Есть таже Scala. Там есть макросы, и вот мой реальный опыт: макросы пишутся в специфичных случаях и чаще всего в библиотеках. Ради этого стоит потерпеть всякие странности при разборе AST. Более того, там есть квази-цитаты и написание макросов не так уш и ужасно, как вы описываете. Калечить язык ради макросов — это безумная идея. от которой веет юношеским максимализмом за версту. Не зря у Луговского столько поклонников у молодежи :)
Лисперы говорят, что к скобочкам привыкаешь и через некоторое время начинаешь сквозь них видеть семантику так же как и на других языках. Вы пробовали? Опишите свои ощущения.
Тут дело скорее в том, что я не понимаю зачем к ним привыкать, если мне не нужно писать макросы в промышленных масштабах :)
По своему опыту могу сказать, что макросы нужно очень часто, а генерация кода сильно распространена, и очень костыльно делается без лиспа. Например я сталкивался с кастомной генерацией миграций для бд, генерацией кода обработки сообщений системами на двух разных языках из общего описания протокола, просто генерацией нескольких на 90% идентичных модулей кода для разных узлов кластера имеющих в себе немного разный код. Практически ничего из этого нельзя было получить в рантайме использовавшихся в этих проектах языков.

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

image
>но лично мне не понравилась идея писать велосипеды для всего из-за того что в самом языке такого нет

И мы, конечно же, увидим примеры?
Извините, никогда не поверю, что у вас так много задач связанным с метапрограммиронием, что готовы работать с языком практически без синтаксиса. Складывается ощущение, что народ просто начитался Луговского, и на лиспах делает тоже, что и все — запросы в БД пуляет, что-то делает с результатами и отрисовывает, а себя успокаивает тем, что «зато тут макросы и метапрограммирование можно делать».
Тут вы правы — я совершеннейшее быдло с быдлозадачами. Мой поинт не в том что ЛИСП синтаксис — манна небесная для всех, а в том, что нельзя сказать что ЛИСП фигня на основании того, что быдлокодеру первые десять минут в нем непривычно. Чтобы понять, так это или не так, надо:
  • во-первых, испытывать его на небыдлокодерских задачах
  • во-вторых, дать себе некоторе время привыкнуть к такому подходу


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


Ага, особенно Common Lisp)
Эта тема уже много раз поднималась в интернетах, любой более-менее сложный проект всегда состоит из «башни» языков, и лисп как раз позволяет их выразить самым гибким и естественным образом.
на лиспах делает тоже, что и все — запросы в БД пуляет, что-то делает с результатами и отрисовывает

Вы чертовски правы! Clojure, если мы говорим про современный Лисп, это в первую очередь язык общего назначения для решения общих задач. Только вот никто себя ничем не успокаивает, а берут и пользуются. Просто вы исходите из предположения, что у Lisp-а тяжёлый синтаксис, что писать `(+ 3 5)` это неимоверно сложнее, чем `3 + 5`, а скобочки — мировое зло. Но то же самое можно сказать и про C, и про Haskell, и про Erlang и даже про математику, и во всех случаях упрёк в «непонятном синтаксисе» будет ошибкой. Любой синтаксис нужно учить, к любому синтаксису можно привыкнуть, и в случае Lisp-а кривая привыкания гораздо меньше, чем вам представляется.
Вы меня с кем-то путаете, я с лиспами знаком, в частности баловался со Scheme, и какое-то время с Clojure.

1) В своем комментарии я критикую не скобочки, я критикую метапрограммирование как таковое для повседненвных задач. Это не про лисп, и даже не только про макросы. Если кто-то пишет на Java и налево и направло использет рефлекшн — такой код гумно.

2) Скобочки тоже отстой, так как синтаксис имеет значение для читабельности. Дело тут не в привычке, а в том, что называется cognitive load, а не в странности. Все ситнаксисы странные на первый взгял, тут вы правы. Но я не про странность, а про бедность, глазу не за что зацепиться. К примеру в Scala рекомендуется вместо foo.map(bar(_)) писать foo map { bar(_) }, что бы небыло переизбытка одинаковых скобочек. И только Лисперы везде со своим неизменным «синтаксис не имеет значения». Это все привлекательно для неокрепших умов (сам такой был) и для теоретиков, но не для реального мира.
Нет, я вас ни с кем не перепутал, и даже привёл цитату, на которую ответил.

1) в своём комментарии вы критикуете синтаксис языка, на что я и отреагировал;

2) как вы посчитали cognitive load для ситнаксиса Скалы и Лиспа? У вас есть какие-то критерии, или вы опираетесь на своё собственное восприятие? Посмотрите, например, на этот файл, и скажите, какой из методов вами с трудом воспринимается, а затем покажите, как бы вы его переписали на Scala, чтобы «было понятнее».
Скобочки тоже отстой, так как синтаксис имеет значение для читабельности. Дело тут не в привычке, а в том, что называется cognitive load, а не в странности.

На мой взгляд, когда скобочкам уделяют так много внимания, то тут, как говорится, за деревьями скобочек не видят леса (скобочек :). Серьезно, современные языки программирования дружно движутся в сторону функциональщины, и если большинство проблем решается с помощью функциональной композиции, то аргумент, что в лиспе скобочки слишком необычны и создают очень большую когнитивную нагрузку, слегка преувеличен. К примеру, вот небольшая, но практичная программа на языке Wolfram:

Graphics[{White, Riffle[NestList[Scale[Rotate[#, 0.1], 0.9] &, Rectangle[], 40], {Purple, Red}]}]

Та же программа записанная в лисп-синтаксисе:

(Graphics{White, #(Riffle(NestList(Scale(Rotate %, 0.1), 0.9), (Rectangle), 40), [Purple, Red])})

Найдите 10 отличий. Кроме того, на мой взгляд, функциональная программа записанная в лисп-синтаксисе создает меньшую когнитивную нагрузку на читателя, это можно увидеть на примере литерала функции #(%) — определенно, в лисп-версии проще понять какой именно объект в тексте является телом литерала функции.
И только Лисперы везде со своим неизменным «синтаксис не имеет значения».

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

(do ((x 1 (1+ i))
     (xs () (conj x xs)))
    ((= x 10) xs))

У этого когда очень плохой синтаксис, и не потому что это лисп и тут много скобочек, а потому что у него есть альтернативный вариант с лучшим синтаксисом:

(loop for x from 1 to 10 collect x)

Или другой альтернативный вариант с еще более лучшим синтаксисом:

(loop {for x from 1 to 10} {collect x})
Вот именно поэтому комментарием выше я и написал, что нужно говорить не про синтаксис, а про ключевые фичи языка. Но пусть будет по-вашему, сконцентрируемся на этой странной нотации с префиксной записью.

Начнём с того, что в C-подобных языках есть два понятия — функции и операторы. В чём разница между ними? А разница только в наборе используемых символов, в остальном операторы — это те же функции. Ну и ещё в том, что в школе мы привыкли писать операторы меежду переменными, а функции — перед и со скобочками.

И привычка — это, в принципе, тоже немаловажный параметр. Всё-таки, зачем переучиваться, если можно этого не делать. Но тут появляется ещё одна ключевая фича всех (ну почти) Лиспов — метапрограммирование.

Допустим, где-нибудь в соседнем языке, скажем, Python, появилась новая фича — автоматическое закрытие файлов:

with open('/path/to/file') as f:
    f.read()


Если вы пишете на каком-нибудь «нормальном» языке с «полноценным» синтаксисом, инфиксиными операторами и всем таким, для введения такой фичи вам придётся менять парсер языка, что как бы не совсем операция уровня пользователя языка. А вот в Лиспах вы можете сделать это всего парой строк кода, примерно так:

;; определение новой фичи языка
(defmacro with-open-file [filename mode alias body]
   `(progn
       (let [~alias (open ~filename ~mode)]
          ~@body
          (close ~alias))))

;; и пример использовнаия
(with-open-file "/path/to/file", "r", 'f
    (read f))

На Clojure, правда, я уже давненько не писал, поэтому тут, скорее всего, куча ошибок, но в целом, думаю, принцип понятен.

Если коротко, макросы позволяют вам (среди прочего!) генерировать код во время компиляции (строго говоря, это не compile, а macroexpand time, но к чёрту подробности). При этом вы работаете с кодом как со списком, а Лисп чертовски хорош в вопросе обработки списков. В итоге мы получаем язык, в котором код — это список, есть куча инструментов для обработки списков и специальный механизм для догенерации кода «на лету». Чувствуете профит?

Автоматическое закрытие файла — это далеко не единственный вариант использования макросов. Например, вы можете сгенерировать специфичный код для каждой платформы или рантайма, написать новый условный оператор или цикл, автоматически дифференцировать функцию, переписать выражение для повышения его эффективности (например, на GPU или BLAS) и ещё кучу всего. Вопрос только в том, как сделать работу с выражениями наиболее удобной. А точнее, как сделать удобной работу с AST — абстрактным синтаксическим деревом.

Вообще говоря, AST можно получить для любого языка с любым синтаксисом и делать с ним что хочешь (хотя и не всегда средствами самого языка, естественно). Но вот сколько разных вариантов узлов вы получите — это уже зависит от языка. Помнится, как-то мне довелось писать статический анализатор кода на JavaScript, так вот, это было страшно. Определение переменной, присваивание, объявление обычной и анонимной функции, условные конструкции, циклы, объекты — оххх. В Лиспах всё гораздо проще. По сути, у вас есть всего два типа узлов: атомы, которые записываются так:

nil
:yes
true


и s-выражения, которые выглядят так:

(+ 3 5)
(def inc [x] (+ x 1))
(inc 41)


Причём первый элемент s-выражения — это всегда объект, который применяется к остатку выражения (что в синтаксисе Лиспа равносильно остатку списка). Если первый символ — это функция, то мы имеем дело с вызовом функции, если оператором — тоже самое, если специальная форма (это что-то вроде специальной встроенной функции) — то с выполнением этой формы и т.д.

В общем-то, при желании такое единообразие при работе с AST можно достичь на многих языках с «нормальным» синтаксисом. Например, Julia также позволяет писать макросы, причём также достаточно простым синтаксисом. Но только в Julia перед тем как переработать код вам нужно его сначала разобрать в своей голове в синтаксическое дерево, в то время как в Лиспе вы видете это дерево непосредственно в коде, в этом самом «странном» синтаксисе. Вы не верите, что это может быть удобно? Тогда вы будете удивлены, что изначально Джон Маккарти — создатель первого Лиспа — использовал префиксную нотацию только для простоты реализации и собирался вскоре её заменить на «нормальный» синтаксис, но коллеги его остановили, сказав, что префиксная нотация таки лучше. Вот такой парадокс.

В общем, если будет интересно, попробуйте на досуге написать пару-тройку простых программ на Clojure. К синтаксису вы привыкните довольно быстро, а вот на новые возможности будете смотреть с широко открытыми глазами. Причём даже не важно, на каком языке вы пишете сейчас — Lisp найдёт, чем вас удивить.
Макросам место в общеиспользуемых и общеизвестных билиотеках. Умельцам, которые на каждый чих расширяют синтаксис нужно пальцы пооткручивать. Тако подход годится что-то там у себя дома на коленке пописать по приколу. С промышленной коммандой разработкой это не имеет ничего общего.
С тем же успехом можно сказать что не надо переиспользовать код и писать функции. Я в детстве так доходил до ограничения vb6 в 64кб на функцию, тогда я объявлял ещё одну и звал её в конце первой.
Функции желательно тоже использовать из общеизвестных бибилиотек, а не велосипедить свои. При этом в их переиспользовании нет ничего плохого, так как остаешся в рамках одного языка. С макросами по сути придется учить свой диалект в каждом случае. Все это детсад и фантазерство.
В бизнес логике код тоже часто может повторяться. А самое интересное начинается когда подходишь к некоторой задаче, которую в общедоступных библиотеках ещё не решали. Т.е. мы бы не увидели никогда ext4 или postgresql, если бы их разработчики думали так же — зачем мы будем это всё городить, если вон уже есть mysql и fat32.
Если есть хоть какая-то специфика предметной области, функции/классы/методы рационально писать свои чтобы не повторять код в каждом конкретном месте. Вопрос — почему макросы настолько отличаются от функций, что нельзя писать свои.
>Вопрос — почему макросы настолько отличаются от функций, что нельзя писать свои.

Функции не привносят какой-то новой семантики, не меняют порядка вычислений. Выучив один раз язык без всяких расширений, можно без пробоем быстро вникнуть в любой проект или библиотеку. Я это не по наслышке знаю, я пишу на Scala, в библиотеках часто используют макросы, так вот каждый раз, когда изучаешь такую библиотеку, ощущение такое, что учишь новый язык. Меня эта особенность сильно отталкивала. Компромис я нашел в том, что выбрал для себя стек технлологий, в котором я более-менее знаком с макро-расширениями. За написание своих макросов нужно решительно бить по рукам.
Я это не по наслышке знаю, я пишу на Scala, в библиотеках часто используют макросы, так вот каждый раз, когда изучаешь такую библиотеку, ощущение такое, что учишь новый язык.

Вот теперь понятно, откуда растёт ваша неприязнь к макросам :D Scala — это абсолютно фантастический язык программирования, который позволяет написать запутанный код даже без применения каких-то продвинутых фич языка. Вот, например, пример объявления REST endpoint на Spray — одном из самых популярных веб-фреймворков:

def objectExitss: Route = get {
    pathPrefix(ApiVersion) {
      pathPrefix("group" / IntNumber) { gId =>
        path("objects" / IntNumber) { objId =>
          traceName(Traces.ObjectExists) { ctx =>
            // handler code goes here
          }
        }
      }
    }
  }


На каком-нибудь Compojure такой же ендпойнт выглядит так:

(defroutes myapp
  (GET "/v1/group/:groupId/objects/:objId" [groupId objId] <handler code goes here>))


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

Макросы нужны как раз для того, чтобы делать код проще, а не сложнее, и мне сложно вспомнить хотя бы один случай хотя бы в одном Лиспе из тех, с которым я работал, где это было бы иначе.
При желании на любом языке можно написать бяку.
А код на спрей должен выглядеть так:

def objectExitss = get { path(ApiVersion / "group" / IntNumber / "objects" / IntNumber) { (groupId, objId) =>
  <handler code goes here>
}}


Это при том, что я спрей не знаю и никогда не использовал.

Ну и по мелочи: ApiVersion у вас забит прямо в код. Отличный план!
groupId и objId у вас строки. Ну а что такого — язык-то без строгой типизации, так что отдать валидацию числовых параметров в «handler code» — это нормально.
При желании на любом языке можно написать бяку.

Так ведь и я о том же. Только в Scala такой бяки хоть лопатой ешь (навскидку — dispatch с его operator-based синтаксисом, json4s со своим DSL, akka-streams, синтаксиса которого пока не понял ни я, ни один мой знакомый). Макросы в Лиспе не ухудшают ситуацию, не вводят нового непонятного синтаксиса, а наоборот унифицируют подход, делая код понятнее. При условии, что вы не пытаетесь сопротивляться префиксной нотации и скобкам, конечно.
json4s со своим DSL

Я для него написал макрос, по аналогии с макросами Json.format[CaseClassName] из Play. Зачем там DSL вообще использовать не представляю.

akka-streams

Для той задачи, что они решают, вроде довольно вменяемо. Хотя я пока глубоко не лез.

Макросы в Лиспе не ухудшают ситуацию, не вводят нового непонятного синтаксиса, а наоборот унифицируют подход, делая код понятнее.

Вообще-то нет. Хорошие макросы делают код понятнее. Плохие делают код ужасным. Так везде, где есть макросы.
Лично я бросил clojure после двух решающих моментов:
1. Типизация. Нечто под названием Typed Clojure оказалось для меня слишком сырым и запутаным. И без поддержки в IDE.
2. Макросы везде. Даже чтоб объявить функцию. Само по себе это не плохо, но это полностью уничтожает аргумент про отсутствие синтаксиса и голое AST. Не зная во что развернутся макросы, даже не представляешь итоговое AST. На мой взгляд макросы создают тот самый синтаксис, просто довольно неудобный.

При условии, что вы не пытаетесь сопротивляться префиксной нотации и скобкам, конечно.

«Расслабьтесь и получайте удовольствие»?
Я для него написал макрос, по аналогии с макросами Json.format[CaseClassName] из Play. Зачем там DSL вообще использовать не представляю.

Вы, может, и не используете, но на официальном сайте около половины страницы посвящено именно DSL-ю.
Из последних изменений, где нужен был json4s DSL — нужно было разобрать JSON, в который никак не имеет и не должен иметь соответсвующего case class, на который этот JSON можно отобразить.

Для той задачи, что они решают, вроде довольно вменяемо.

К чёрту задачу, объясните, что здесь происходит, и почему на второй стрелочке наш поток данных разделился на 2?

in ~> f1 ~> bcast ~> f2 ~> merge ~> f3 ~> out
                  bcast ~> f4 ~> merge

Я верю, что в этом можно разобраться, но если это более интуитивно, чем макросы в Лиспе, то в мире явно что-то не так.

Лично я бросил clojure после двух решающих моментов:

Я надеюсь, это мнение, а не аргумент? Если мнение, то я его понимаю: мир действительно делится на людей, которые любят динамическую типизацию и которые её ненавидят. Если вы во второй группе, то ничего не имею против, но на самом по себе языке программирования это никак не сказывается.

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

Есть такая штука как `macroexpand`, которая снимает все вопросы.

«Расслабьтесь и получайте удовольствие»?

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

На официальном сайте Play ручной работе с json уделено несколько страниц, против одной для макроса. Это не отменяет того факта, что использовать надо именно макрос.
нужно было разобрать JSON, в который никак не имеет и не должен иметь соответсвующего case class, на который этот JSON можно отобразить

Если ваши json нельзя внятным образом отобразить на вменяемого вида структуру данных, то это проблема не DSL, а того, кто json составлял.

Есть такая штука как `macroexpand`, которая снимает все вопросы.

И к чему тут это? Предлагаете мне весь код, прежде чем его читать, прогнать через macroexpand? Ведь при чтении не известно является ли данная функция макросом или нет (макросы не выделены явно, как в Rust).
В scala тоже для любого выражения можно легко посмотреть десахареный код (reify), или любую стадию компиляции. Последний раз я так делал чтоб потестить самописный макрос для pattern matching xml — у меня одна строка развернулась в 180 строк. Почитаешь такое.

Я же не говорю, что в лиспах невозможно посмотреть итоговое AST. Я утверждаю, что все заявления про отсутствие синтаксиса и «написание программ на AST» — «маркетинговый булшит». На самом деле синтаксис есть и код имеет крайне отдаленное отношение к AST, просто надо как-то оправдать нечитаемость кода.

Естественно, если вы изначально настроены против чего-то и активно сопротивляетесь новым концепциям

Понавешиваем ярлыков? То есть Perl, JS, C#, Java, Scala, R, Haskell и куча других языков, на которых я с огромным удовольствием писал код (где-то больше, где-то меньше) — это всё старое, банальное и построенное на одинаковых концепциях, а вот лиспы — единственный источник новых концепций?
Если ваши json нельзя внятным образом отобразить на вменяемого вида структуру данных, то это проблема не DSL, а того, кто json составлял.

Составителю JSON глубоко плевать, ложится ли его строка на какую-то вашу структуру. В реальном мире вы не можете диктовать другим людям, как им писать их JSON.

Так или иначе, вопрос был в том, используется ли в Scala DSL. И ответ — да, широко используется.

И к чему тут это? Предлагаете мне весь код, прежде чем его читать, прогнать через macroexpand?

Вы спросили, как узнать AST после развёртывания макроса, я вам ответил. Прогонять всё через macroexpand не надо, также, как и не надо прогонять все функции через дебаггер или все синтаксические конструкции через парсер языка. Вы просто берёти и пользуетесь.

Последний раз я так делал чтоб потестить самописный макрос для pattern matching xml — у меня одна строка развернулась в 180 строк. Почитаешь такое.

Вот поэтому вы и считаете макросы чем-то страшным. А возьмём какой-нибудь макрос из Lisp, например, `time`, замерающий время выполнения выражения. Сами догадаетесь, как он реализован? Я бы сказал, как-то так:

(defmacro time [expr]
   `(let [start (current-time)]    ;; берём текущее время
     (let [result ~expr]              ;; выполняем выражение
        (println (- (current-time) start))  ;; считаем, сколько прошло, и печатаем на консоль
        result)))                          ;; возвращаем результат


А выражение `(time (foo))`, соответсвенно, раскроется в:

(let [start (current-time)]   
   (let [result (foo)]           
      (println (- (current-time) start))  
      result))                        


Т.е. в макросе вы уже задали шаблон кода, а потом просто попросили компилятор запомнить этот шаблон под именем `time`, и дальше в коде можете писать только это имя + своё выражение.

Как реализованы макросы открытия/закрытия ресурсов, и что получается после генерации, я думаю, вы сами можете догадаться. Есть и более сложные макросы, например, в Julia я видел макрос, который создаёт цикл нужной вложенности, а сам писал макрос `@runduring`, который выполняет выражение заданное количество раз. Но ни в одном из этих случаев сгенерированный код и близко не приближался к 180 строкам, и на 90% был написан человеком. Так что при правильном подходе макросы абсолютно прозрачны.

Я утверждаю, что все заявления про отсутствие синтаксиса и «написание программ на AST» — «маркетинговый булшит»

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

просто надо как-то оправдать нечитаемость кода.

Я уже устал повторять, что код на Lisp читаем и очень легко. Для меня гораздо проще понять, что проиходит в исходном коде Compojure, чем в коде Spray, даже при том, что на Clojure я писал немного меньше и довольно давно. Но вы взяли себе за аксиому, что у Lisp тяжёлый синтаксис, и делаете все выводы на основе этого.

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

Понавешиваем ярлыков?

Речь шла про абстрактного лирического героя (см. «я постоянно встречаю людей, которые...»). Про вас лично я ничего не знаю и выводов никаких не делаю.

единственный источник новых концепций

Новых для вас. А вернее, для того же лирического героя. Ещё раз: если некоторый человек сам активно сопротивляется воспринятию новых концепций, то стоит ожидать, что они ему не понравятся.
Вы спросили, как узнать AST после развёртывания макроса, я вам ответил.

Где я такое спрашивал?

А возьмём какой-нибудь макрос из Lisp, например, `time`
макросы открытия/закрытия ресурсов

Возникает только 1 вопрос: зачем тут вообще макросы?
def time[T](f: => T): T = {
  val start = currentTime()
  val res = f
  val end = currentTime()
  // use start and end here
  res
}

time { longOperation() }

Я как-то привык использовать макросы для чего-то существенного. Хотя бывают и маленькие макросы, например позволяющие выиграть пару микросекунд при отключенном логировании. Но для меня это скорее исключение.

Так что при правильном подходе макросы абсолютно прозрачны.

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

И кому тогда, по вашему мнения, лисперы пытаются «продать» свой язык?

Каждый фанат clojure, с которым я разговаривал, начинал перечисления плюсов с «пишешь на голом AST». Не знаю откуда они все это берут. Но фраза оторвана от реальности полностью.

К Лиспу люди приходят сами, и только сами.

Меня, видимо, в scala и другие языки насильно тащили.

Для меня гораздо проще понять, что проиходит в исходном коде Compojure, чем в коде Spray, даже при том, что на Clojure я писал немного меньше и довольно давно.

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

Но вы взяли себе за аксиому, что у Lisp тяжёлый синтаксис, и делаете все выводы на основе этого.

Откуда у вас такие выводы обо мне? Любой, кто считает лисп нечитаемым, предвзят?
Я даже смог научиться читать лисп-код, отформатированный определенным образом немногим хуже, чем я читаю питон. Но он все так же неудобен и не имеет плюсов. Его главный рекламный плюс — голое AST — разбивается о засилие макросов.

Речь шла про абстрактного лирического героя

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


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

Мы оба отлично знаем, что и на лиспе и на скале можно писать DSL, так что задача «переписать не хуже» сводится к «реализовать похожий синтаксис как DSL» и тривиально решается. Это софистика.

То же, что действительно так не повторить, например код, использующий typeclass, вы назовете не нужным.
Эти два примера оба используют достаточное количество magic. Их понятность субьективна, во что разворачивается код мне очевидней в случае со Scala, я же на ней пишу :) Устройства вашего код на Clojure для меня абсолютная загадка.

P.S. и таки да, вам уже правильно написали, коды действительно неэквиваленты. Код на Scala делает больше — он привносит типипзацию как минимум.
Эти два примера оба используют достаточное количество magic.

Magic? Ну если вы считаете макросы магией, то да. Для Лисперов это просто инструмент. Но вопрос был не в магии, вопрос был в изучении нового языка. Сколько вам нужно времени, чтобы запомнить, что `defroutes` объявляет список роутов? А сколько времени нужно, чтобы запомнить все детали синтаксиса объявления роутов в Spray? Я вот до сих пор с первого раза написать не могу.

Я это всё к тому, что макросы — это ну ни разу не новый язык, и в вашем случае проблема именно в Scala, а не в макросах.

P.S. и таки да, вам уже правильно написали, коды действительно неэквиваленты. Код на Scala делает больше — он привносит типипзацию как минимум.

Ну это вы уже придераетесь — если нужно, добавить проверку типов — минутное дело. Версия — тоже как бы не проблема, причём её можно добавить прямо как параметр макроса.
> Сколько вам нужно времени, чтобы запомнить, что `defroutes` объявляет список роутов?

Столко-же, что бы и запомнить, что path задает роут в Spray. И совершенно не ясно как defroute работает внутри. В spray можно хотя бы по сигнатурам функция посмотреть что происходит. У вас нечто, что генерит на выходе другое нечто.

>и в вашем случае проблема именно в Scala, а не в макросах.

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

> макросы — это ну ни разу не новый язык

Как это не новый? Макросы по определению — это расширение языка.
И совершенно не ясно как defroute работает внутри. В spray можно хотя бы по сигнатурам функция посмотреть что происходит. У вас нечто, что генерит на выходе другое нечто.

И вам я тоже расскажу про замечательную штуку под названием `macroexpand`.

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

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

Как это не новый? Макросы по определению — это расширение языка.

А что такое расширение языка? Функция `test("...") {..}` из ScalaTest — это расширение языка?
>И вам я тоже расскажу про замечательную штуку под названием `macroexpand`.

Это я и называю «учить новый язык». Когда я юзаю обычные функции у меня не возникает мыслей смотреть как оно там реализовано. Никакой магии нет, главно прочитать сигнатуру, сразу понятно что передать и что вернется. А когда надо лезть внутрь, это уже плохо.

>Если макрос решает проблему, то логично его использовать/написать, вне зависимости от того, что это за код.

Человеку, который будет работать с вашим кодом придется смотреть этот ваш macroexpand, а потом в голове держать то, что он увидел. Нужно уменьшать cognitive load всеми способами. Для общеиспользуемых либ это оправданное зло, оправданное тем, что либа используется постоянно и всеми.
Это я и называю «учить новый язык». Когда я юзаю обычные функции у меня не возникает мыслей смотреть как оно там реализовано. Никакой магии нет, главно прочитать сигнатуру, сразу понятно что передать и что вернется. А когда надо лезть внутрь, это уже плохо.

Я извиняюсь, вы вообще макросы писали? Макрос — это и есть функция, просто исполняется она на этапе компиляции. И у неё тоже есть сигнатура, строка документации и всё, что присуще обычной функции. Просто вы захотели посмотреть, как работает `defroute`, поэтому я предложил `macroexpand`, который для обычных функций эквивалентен вызову функции. Так что никакой магии тут и рядом нет, как вы сами сказали, достаточно просто прочесть сигнатуру.

Человеку, который будет работать с вашим кодом придется смотреть этот ваш macroexpand, а потом в голове держать то, что он увидел.

Аналогично, если вам не интересен сегенированный код (а большинстве случаев он вам не должен быть интересен), то ничего раскрывать и запоминать не надо. Просто посмотрите сигнатуру или документацию. Документация, кстати, достаётся прямо в REPL через:

    (doc myfunction)
    (doc mymacro)
>просто исполняется она на этапе компиляции.

Это вносит целый слой абстракции в код и усложняет понимание.

>Аналогично, если вам не интересен сегенированный код

Мне как стороннему наблюдателю приходится читать определение макроса всегда. Это вам как автору не интресно, так как вам и так все очевидно.

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

Это вносит целый слой абстракции в код и усложняет понимание.

Т.е. вопросов с сигнатурой макросов больше нет? И то хорошо.

Мне как стороннему наблюдателю приходится читать определение макроса всегда.

Вам неочевидно, как реализован `time` или `with-open-file`? Океей.

Мой опыт говорит о том, что нужно ограничивать использование продвинутых штук в прикладном коде вроде макросов

Ваш опыт программирования на каком языке? Можете привести пример, где макрос в Lisp вызвал проблемы? Не имплиситы в Scala, не макросы в Scala, а макросы в Lisp, которые в реальном коде привели к ошибкам, замедлению программы или другим неприятным побочным эффектам. Случай из жизни, пожалуйста, на основе которого вы делаете вывод, что макросы опасны и их использование стоит ограничить.
>Можете привести пример, где макрос в Lisp вызвал проблемы?

А что вам пример, я покажу, вы мне скажите, что вам все понятно. Ну ооок.
А мне было бы интересно посмотреть. Приведите, пожалуйста!
Я не просил привести непонятный макрос, я просил привести пример, где макрос вызвал проблемы. Например, потратить 4 час на поиск implicit параметра, отвечающего за таймаут запроса — это проблема, вызванная именно неаккуратным использованием advanced feature. Или не иметь возможности нагуглить нужный метод, потому что все методы выражены через операторы — это проблема. Проблемы ведут к увеличению времени разработки и к ошибкам в продукте. Вы можете привести пример из жизни, где макрос в Лиспе вызвал что-нибудь из этого?
потратить 4 час на поиск implicit параметра, отвечающего за таймаут запроса

Это как? Даже если вы не используете IDE, есть нативные методы получения всех имплиситов на месте (аналог macroexpand для имплиситов). Вы действительно 4 часа имплисит искали или 4 часа выясняли, что проблема в таймаутах? Если первое, то, как и в случае со спринком, это лишь ваше неумение пользоваться инструментом. Если второе, то причем тут фичи языка? Было бы легче, если бы таймаут был зарыт глубоко в конфигах?
Вы действительно 4 часа имплисит искали или 4 часа выясняли, что проблема в таймаутах?

И то, и другое. Документация по Spray указывала на один тип переменной, для таймаута, а оказалось, что тип должен быть другим, поэтому наше первое решение — объявление имплисита для таймаута — тупо не работало. Причём дефолтный имплисит правильного типа был определён где-то далеко в иерархии классов Спрея и передавался в одно из не самых очевидных мест, из-за чего искать его, даже имея твёрдую уверенность, что проблема в таймаутах, было сущим наслаждением.

Чтобы найти таймаут в конфиге по иерархии функций, в каждой из которых он передаётся явно, нужно всего несколько кликов в Intellij. Если вы знаете быстрый способ найти некий параметр неизвестного типа передаваемый неявно неизвестно откуда и неизвестно куда, пожалуйста сообщите.
Если вы знаете быстрый способ найти некий параметр неизвестного типа передаваемый неявно неизвестно откуда и неизвестно куда, пожалуйста сообщите.

Idea «Ctrl Shift P» — показ имплиситов в данном вызове.
show + reify — аналог macroexpand — снимает весь сахар с выражения
"+Xprint:typer" компилятору — показ фазы typer (или любой другой, достаточно заменить имя фазы). Как избавляет вообще от всего сахара по файлу. Если файлов много — включите инкрементальную компиляцию и измените целевой.
Idea «Ctrl Shift P» — показ имплиситов в данном вызове.

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

Параметр по умолчанию (язык может быть почти любой — с точностью до способа реализации параметров по умолчанию. В лиспах это перегрузка по количеству аргументов):
class ParentClass {
  public ResultType method(Type1 param1, TimeoutType timeout) {...}
  public ResultType method(Type1 param1) {
    return method(param1, getDefaultTimeout())
  }
}


implicit:
class ParentClass {
  public method(param1: Type1)(implicit timeout: TimeoutType): ResultType = ???
}

object ParentClass {
  implicit def defaultTimeout: TimeoutType = ???
}


В чем тут вообще может быть разница при поиске?
Вы забыли написать вызов этих методов.

Даю подсказку: имплисит параметр пришёл из родительского класса и вообще находится в другом файле.
Их вызовы одинаковы:

class ChildClass extends ParentClass {
  def myMethod = {
    ...
    method(param1)
    ...
  }
}
Ах, простите, прочитал в спешке. На самом деле достаточно и первоых двух кусков кода. Вызовы в них абсолютно неэквивалентны, ибо при дефолтном параметре мы точно знаем, что либо параметр передаётся явно (сигнатура с двумя параметрами), либо берётся дефолтное значение, определённое там же (сигнатура с одним параметром). С имплиситами мы только знаем, что этот параметр где-то определён и влияет на какие-то функции. Возможно, функция одна, возможно, несколько, возможно имплисит переменная объявлена в том же классе, в родителе, в родителя родителя, в трейте или импортируется через `import blablabla._`. В общем, вы, конечно, можете считать нас всех идиотами, но мы действительно потратили на поиск нужного параметра ~4 часов.
Я никого идиотами не считаю. Заковыристую ошибку можно и дольше искать.
Я лишь хочу показать, что если вы не знаете что ищите, то имплиситы ничуть не хуже параметров по умолчанию. Если же знаете, что ищите, то, надеюсь, мои советы выше помогут разобраться с имплиситами в другой раз за время от 5 секунд до 5 минут.
Я это всё к тому, что макросы — это ну ни разу не новый язык

Это какие-то бедненькие макросы. Макросы нужны как раз когда нужен новый язык, например.
Вот я и говорю, что у вас мозг, испорченный Скалой. Посмотрите макросы, опять же, здесь — и попробуйте найти там что-то сложное, тянущее на «новый язык», который нужно ещё отдельно учить.
Такую мелочь в нормальных языках делают без макросов. Макросы подключают для чего-то более существенного.
К слову о существенном: вот тут есть макросы?
(defroutes myapp
  (GET "/v1/group/:groupId/objects/:objId" [groupId objId] <handler code goes here>))

Если да, то зачем дублирование ":groupId/objects/:objId" и "[groupId objId]"? Мы уже меняем AST, язык уже динамически типизированный, зачем оставлять возможность ошибки и дублировать код (имена переменных)?
Такую мелочь в нормальных языках делают без макросов.

Ну что ж, если вы ещё до дискуссии считаете Lisp ненормальным языком, считаю продолжение разговора бессмысленным.
Замените «нормальные» на «обычные». Могу даже уточнить «в языках, не декларирующих отсутствие синтаксиса одной из основных фич».

Я готов критиковать List-подобные языки, на в данном случае не вкладывал негативного оттенка умышленно. Для меня необычный/ненормальный (в применении к ЯП) еще не значит «плохой». Точно так же я готов противопоставлять Haskell и «нормальные языки», при том, что о Haskell у меня крайне положительное мнение.

Мы, несомненно, тут холиварили, но до банально ругани опускаться никто не планировал.
Замените «нормальные» на «обычные». Могу даже уточнить «в языках, не декларирующих отсутствие синтаксиса одной из основных фич».

Отсутствие синтаксиса — это неправильная интерпретация схожести синтаксиса с AST. Скобки, двоеточие, ковычки и т.д. — это тоже синтаксис. Макросы, кстати, не вводят новый синтаксис — это всё тот же application, только теперь это macro application, а не function application. Суть аргумента обычно в том, что писать парсер для s-expression (в т.ч. парсер внутри языка, т.е. макрос) очень просто — банально считайте текст как лисповский список и у вас уже есть AST. Если кто-то утверждает полное отсутствие синтаксиса, можете смело говорить, что они неправы со ссылкой на меня.

И нет, даже в Julia со вполне традиционным синтаксисом макросы используются довольно широко.

Макросы подключают для чего-то более существенного.

Это вы сами для себя придумали. Макросы подключают там, где это уместно и где решение через макросы наиболее простое и элегантное. Пример с `time`: в Scala есть необязательный синтаксис, который позволяет опустить дополнительные скобки и выражение `x => ` вначале передаваемой лямбда-функции; в Clojure синтаксис более строгий, опустить аргумент нельзя, и поэтому удобней то же самое реализовать через макросы. Существенная ли эта фича? Не особо. Нужно ли её реализовывать через макросы в Lisp? Одназначно да.

Если да, то зачем дублирование ":groupId/objects/:objId" и "[groupId objId]"?

Вопрос стиля — в Clojure не принято убирать все «очевидные» элементы синтаксиса. Но да, если бы вы захотели привнести немного Scala в Clojure, то убрать это «дублирование» не составило бы труда. Причём код макроса увеличился бы примерно на 1 строчку.

Та же функция time или автозакрытие ресурсов во всех языках с лямбдами (C#, Java, JS, etc) пишется тривиально.

А как насчёт генерации N вложенных циклов или переписывания выражания над матрицами на команды BLAS?

Зачем тут вообще могут понадобиться макросы? Что в лиспе мешает определить функцию?
(defn my-defroutes [name & routes]
  (let [new-routes (map trace-name routes)]
      (defroutes name new-routes)))

В макросе `name` и `routes` — это выражения, в функции — аргументы, которые вычислятся ровно перед передачей их в функцию.
Аргументируют обычно если не к отсутствию синтаксиса, то к близости кода к AST. Как раз ваш аргумент про легкость парсинга s-expression.
Если это легкость понимания человеком итогового AST — а именно с таким аргументом я обычно сталкивался, то именно этот аргумент разбивается о засилие макросов. Пока (мысленно или программно) не выполнишь все макросы (которые на каждом шагу) не сможешь представить себе итоговое AST.
Если же это про легкость написания интерпретатора/компилятора/IDE, то это не мои проблемы. Я как раз за то, чтобы авторы языка сделали за меня как можно больше работы и меня не интересуют их страдания. IDE же для динамически типизированных языков довольно бесполезен. Разве что отступы расставит.

Scala есть необязательный синтаксис, который позволяет опустить дополнительные скобки и выражение

Меня в C# и Java не напрягало написать "() => ". Разница с макросами, как ранее вам уже кто-то писал, что достаточно прочитать сигнатуру, чтоб понять принцип работы.
Любой синтаксис «не обязательный». И задача разработчиков языка определить круг часто используемых фич, покрываемых синтаксисом. В таких языках эти фичи составляют стандартный словарь всех разработчиков и использование макроса сразу же сигнализирует «внимание! сейчас будет нечто нестандартное!». В lisp же такого стандартного набора фич нет и нет отличия между стандартным словарем и нестандартными действиями.

Вопрос стиля — в Clojure не принято убирать все «очевидные» элементы синтаксиса.

Вопрос тут не в стиле, а в защите от ошибок. Если бы макрос, не убирая параметры, делал проверку на совпадения имен, было бы уже хорошо. Видимо в языках с динамической типизацией не принято снижать вероятность ошибки.

А как насчёт генерации N вложенных циклов или переписывания выражания над матрицами на команды BLAS?

Вы только что аргументировали к тому, что макросы не создают нового языка и не вносят проблем в понимание. Сейчас же вы предлагаете делать нечто довольно запутаное. И к чему аргумент? Я отвечал на сообщение с перечислением фич скалы, без которых, якобы, невозможно реализовать без макросов то, что в Lisp реализуют макросами.

В макросе `name` и `routes` — это выражения, в функции — аргументы, которые вычислятся ровно перед передачей их в функцию.

Ясно. Я считал, что макросом реализована функция GET, тогда defroutes принимал бы строку и список структур {метод, строка, функция} — в таком случае defroutes не должен быть макросом. Я бы так реализовывал defroutes.
Если это легкость понимания человеком итогового AST — а именно с таким аргументом я обычно сталкивался, то именно этот аргумент разбивается о засилие макросов.

Ок, аргумент, что макрос — это тоже часть AST, вы проигнорировали. Ну да ладно.

Давайте примеры. Проектов на Clojure на гитхабе полно, могу даже свой старый код показать — расскажете, где там засилие макросов.

Разница с макросами, как ранее вам уже кто-то писал, что достаточно прочитать сигнатуру, чтоб понять принцип работы.

И как я уже ответил, кто вам мешает прочитать сигнатуру макроса?

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

Мне нравится, как вы рашаете за разработчиков языков программирования, в чём их задача.

делал проверку на совпадения имен,

А почему они должны совпадать? Я отвечу за вас: потому что вы считаете, что так правильно. Другие считают иначе.

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

Сгенерировать несколько вложенных циклов или заменить `X += z * Y` на `axpy!(z, Y, X)` — это что-то запутанное? Т.е. замена по паттерну сложней, чем вот это?

Аргумент к тому, что юз-кейсы макросов в Lisp не сводятся к анонимный функциям в Scala.

где там засилие макросов

Начнем с defn. Даже чтоб определить именованную функцию уже используется макрос.

И как я уже ответил, кто вам мешает прочитать сигнатуру макроса?

Какая сигнатура макроса? Нет типизации. Читать надо весь макрос.

А почему они должны совпадать?

Не должны, конечно. Чтоб быть более честным там надо написать "/v1/group/:?/objects/:?" просто чтоб не вводить никого в заблуждение.

Сгенерировать несколько вложенных циклов или заменить `X += z * Y` на `axpy!(z, Y, X)` — это что-то запутанное?

Да, потому, что не прочитав ваш макрос не поймешь что он делает и какие замены производит. Он вносит новый не определенный до этого набор допущений и договоренностей.
В том же, на что вы сослались, противоположный случай: есть XPath. Он известен, он хорошо определен, он полезен для решения своего круга задач. Любая попытка написать «аналог на языке» ущербна ибо вносит недоязык, чем-то напоминающий XPath, но не являющийся им.
В случае же того макроса любой кто знает XPath, знает как работает итоговый код, так как там используется не DSL, похожий на XPath, а сам XPath. Если итоговый код работает не по спецификации, то в нем бага.

Аргумент к тому, что юз-кейсы макросов в Lisp не сводятся к анонимный функциям в Scala.

У меня очень сильное подозрения, что все достаточно простые макросы в Lisp сводятся к синтаксическим конструкциям популярных языков. Чаще всего как раз к лямбдам. По крайней мере все простые макросы, что я пока видел.

Кстати, а о чем мы спорим? Я сюда вообще начал писать только когда увидел необоснованно запутанный scala-код.
Начнем с defn. Даже чтоб определить именованную функцию уже используется макрос.

Ах, вот вы о чём. Я просто подумал, что речь идёт о засилии пользовательских макросов, которые «нужно каждый раз разворачивать и изучать». И много времени вам нужно, чтобы, как вы сами говорите, «мысленно выполнить» этот макрос и запомнить, как он реализован (хотя зачем вообще их разворачивать — я до сих пор не понимаю)? Сдаётся мне, что `defn` не создаст вам проблем. Давайте ещё примеры.

Какая сигнатура макроса? Нет типизации. Читать надо весь макрос.

А, теперь проблема в типизации, ок. Вы там выше говорили, что писали на JS. Причём даже с удовольствием. Но JS — это язык с динмической типизацией. В нём вы тоже каждый раз лезли в функцию и читали весь её код?

Динамическая типизация не имеет никакого отношения к макросам. Собственно, тип у аргументов макроса всегда один и тот же — expression.

Не должны, конечно. Чтоб быть более честным там надо написать "/v1/group/:?/objects/:?" просто чтоб не вводить никого в заблуждение.

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

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

Давайте конкретно: какие допущения и договорённости вносит:

  1. Макрос, генерирующий N вложенных циклов
  2. Макрос, заменяющий обычные операции над матрицами на соответсвующие операции из BLAS

?

В случае же того макроса любой кто знает XPath, знает как работает итоговый код

Вы правда не знаете, как работает несколько вложенных циклов?

В общем, не придумывайте проблемы. По вашим словам, макросы вносят полную неразбериху, но при этом во всех примерах вы прекрасно поняли, что получается на выходе.

У меня очень сильное подозрения, что все достаточно простые макросы в Lisp сводятся к синтаксическим конструкциям популярных языков

На вскидку:
  • Компиляция разного кода в зависимости от платформы
  • Компиляция разного кода в зависимости от версии (очень актуально для Scala)
  • Создание N похожих методов с небольшими отличиями, например разным количеством параметров или разными типами аргументов
  • Генерация классов с заданными свойствами (например, с дополнительными полями или кастомными геттарами/сеттерами)

Это всё очень банальные макросы, которые пишутся на коленке за ~3 минуты. Последний случай, кстати, достаточно интересный — примерно таким подходом в Julia в качестве эксперимента добавили traits. Т.е. в языке их как не было, так и нет, но через макросы их без труда добавили.
Ок. Этот разговор ушел куда-то не туда где-то еще в час ночи.

Единственное из-за чего я ввязался в разговор — совершенно кошмарное использование скалы, выдаваемое за вменяемый пример кода на ней (приведенный для сравнения).

Далее наш с вами разговор про макросы начался с того, что я описал свои причины отвернуться от clojure.

Вы назвали систему типов (фактически принудительные юнит-тесты) вкусовщиной. Ок, это ваши фломастеры.

И мы с вами совершенно бессвязно обусждали что-то не видя тезисов друг друга.

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

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

Но мне (и не только мне) он не удобен. Это субъективно. Просто примите существование такой точки зрения. Тут не о чем спорить.

JS

См. выше. В JS очень лаконичный общий словарь, на котором общаются разработчики. И обычно если они выходят за его пределы, то это видно очень явно.

на самом деле сразу догадались, что это за аргументы

Мы с вами точно из разных миров. Я же не о том, что не понятно что имена значат. Я о том, что от инструмента я ожидаю помощи. Для меня имена, которые ни на что не влияют, это воспоминания о плейсхолдерах вида "?" в SQL. Ими пользовались, но от них ушли. В моем мире даже если параметров всего 3-4 при большом количестве инструментов написанных в таком духе и десятках людей, работающих над кодом, такое будет стрелять. Всегда просочится код с перепутанным порядком параметров, не отловленный юнит-тестами. Если у вас не так, то я могу лишь позавидовать.

Для меня всегда одним из основных подходов являлось: «неправильный код должен выглядеть неправильно». `":groupId/objects/:objId" [objId groupId] ...` не выглядит неправильно. Это вообще глазами не цепляется среди остального кода. Ниже я привел пример из Play. Они так сделали не потому, что это прикольно, и не потому, что им нравится писать сложные макросы, а потому, что такой подход снижает вероятность ошибки.

Макрос, генерирующий N вложенных циклов

Способ описания переменных и их диапазонв. А так же возможность указания зависимости диапазона вложенного цикла от текущего значения внешнего.

Макрос, заменяющий обычные операции над матрицами на соответсвующие операции из BLAS

Как минимум список поддерживаемых операций и сложность преобразуемых выражений.

По вашим словам, макросы вносят полную неразбериху

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

На вскидку:

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

Но в лиспе их не малое количество. В лиспе можно встретить большие куски кода, где макросов больше, чем обычных функций и это будет нормально для лиспа. Хорошо это или плохо каждый решает для себя сам. Свои аргументы и свой личный вывод я высказал.
Единственное из-за чего я ввязался в разговор — совершенно кошмарное использование скалы, выдаваемое за вменяемый пример кода на ней (приведенный для сравнения).

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

Вы назвали систему типов (фактически принудительные юнит-тесты) вкусовщиной. Ок, это ваши фломастеры.

Не систему типов, а статическую типизацию. Вкусовщиной — да, очевидно, иначе в мире не было бы столько языков с динамической типизацией. Юнит-тесты тут не причём. Вообще это какой-то дикий миф о том, что в динамических языках вы всё время будете делать ошибки типизации. Ошибки типизации составляют от силы 1% от ошибок, причём все они прекрасно отлавливаются одним запуском функции в REPL. Но это надо просто брать и пробовать, переубедить вас сторонними аргументами мне вряд ли удастся, да и не очень хочется.

Мои тезисы: в «обычных языках» синтаксис вносит общий словарь. Макросы же используются только когда этого словаря оказывается не достаточно, что сразу сигнализирует о сложности происходящего.

Мой антитезис: не сигнализирует. Нет никаких объективных причин считать, что макросы сами по себе сигнализируют о сложности. Прекрасный пример «обычного языка» с большим количеством макросов — Julia. Прекрасный пример абсолютно прозрачного макроса — `@time`. Впрочем, я это всё уже говорил.

Но мне (и не только мне) он не удобен. Это субъективно. Просто примите существование такой точки зрения.

Это не отменяет того факта, что использовать надо именно макрос.

Если ваши json нельзя внятным образом отобразить на вменяемого вида структуру данных, то это проблема не DSL, а того, кто json составлял.

На самом деле синтаксис есть и код имеет крайне отдаленное отношение к AST, просто надо как-то оправдать нечитаемость кода.

Это какие-то бедненькие макросы. Макросы нужны как раз когда нужен новый язык, например.

Такую мелочь в нормальных языках делают без макросов. Макросы подключают для чего-то более существенного.

Окей, если всё это — ваше мнение, и перед каждым предложением вы готовы поставить «я считаю, что», то вопрос закрыт.

См. выше. В JS очень лаконичный общий словарь, на котором общаются разработчики. И обычно если они выходят за его пределы, то это видно очень явно.

Вопрос был про динамическую типизацию. В чём разница между сигнатурой макроса и сигнатурой функции в JS?

Способ описания переменных и их диапазонв. А так же возможность указания зависимости диапазона вложенного цикла от текущего значения внешнего.

Это входные параметры для макроса, которые описываются в его сигнатуре.

Как минимум список поддерживаемых операций и сложность преобразуемых выражений.

Список поддерживаемых операций описан в спецификации BLAS, сложность обрабатываемых выражение, как и в любом оптимизирующем инструменте, зависит от качества реализации. Вы ведь не считаете, что оптимизатор Scala вносит дополнительные допущения и договорённости?

Макросы не вносят полную неразбериху, они увеличивают сложность кода.

Вы снова забыли добавить «я считаю, что...».

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

Если под нормальными функциями подразумевается `defn`, то я, опять же, хотел бы увидеть примеры.

В общем, если вы признаёте, что ущербность динамической типизации, уродливость синтаксиса и сложность макросов — это ваше личное мнение, то дискуссию можно закрывать.
Юнит-тесты тут не причём.

Мой опыт: статическая типизация радикально снижает количество необходимых юнит-тестов.
Ошибки типизации составляют от силы 1% от ошибок,

Откуда статистические данные, да еще и с такой точностью?
причём все они прекрасно отлавливаются одним запуском функции в REPL.

У вас 1 запуск в REPL дает покрытие по путям в 100%?

Мой антитезис: не сигнализирует.

Не сигнализирует в том случае, если манипуляции с AST используются постоянно для всего. Тот же time совершенно не обязательно реализовывать макросом.

если всё это — ваше мнение

«Удобство» и «сложность» в любом случае субъективны. Можно лишь попытаться найти общие индикаторы сложности. Мы с вами уже разошлись в индикации сложности, например по количеству конструкций, меняющих порядок выполнения.

В чём разница между сигнатурой макроса и сигнатурой функции в JS?

В том, что ни какая функция в JS не меняет порядок вычисления своих аргументов — все аргументы вычислятся до вызова. Если в вызове есть лямбда, то при чтении кода она видна.

Это входные параметры для макроса, которые описываются в его сигнатуре.

Можно пример такой сигнатуры из которой будет понятно как устанавливать соотношения между значением во внешнем цикле и диапазоном во вложенном?

Список поддерживаемых операций описан в спецификации BLAS

Меня интересуют не выходные операции, а то, что на вход. Список функций clojure.

сложность обрабатываемых выражение, как и в любом оптимизирующем инструменте, зависит от качества реализации

То есть мы пишем некий код и надеемся, что он как-то будет оптимизирован? Это хоть как-то допустимо для оптимизаций, работающих неявно. Но мы-то говорим о макросе, вызываемом явно! Человек сам его вызывает и надеется на какой-то результат. И проверить результат он может только эмпирически. Так почему бы результат macroexpand не вставить вместо кода, просто чтобы гарантировать, что ни какие последующие изменения, кажущиеся незначительными, не угробят оптимизацию? В этом, кстати, разница с макросом для XPath — он работает детерминировано, такой «оптимизатор» работает «как-то». В следующей версии может заработать по другому. А тесты на производительность крайне трудозатратны.

Вы ведь не считаете, что оптимизатор Scala вносит дополнительные допущения и договорённости?

Я не делаю явные его вызовы в коде. И вообще не рассчитываю на то, что он есть. Когда я пишу код на scala, я считаю, что существует только SLS и ни каких дополнительных оптимизаций. Случаи же, когда я закладываюсь на оптимизации JIT я выделяю крайне явно.

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

Вы снова забыли добавить «я считаю, что...».

См. выше. Если к какому-то вашему утверждению про сложность можно добавить «это не мнение, это факт», то я не знаю как вообще можно обсуждать субъективные понятия.

ущербность динамической типизации

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

уродливость синтаксиса и сложность макросов

См. выше про субъективность.
Если вы считаете, что удобность синтаксиса и простота работы с макросами — это не ваше личное мнение, а факт, то дискуссию закрыть просто необходимо.
Откуда статистические данные, да еще и с такой точностью?

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

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

У вас 1 запуск в REPL дает покрытие по путям в 100%?

Нет, логические ошибки всё равно иногда проскакивают, но неправильные типы — крайне редко. Да и как это возможно вообще? Вы же что-то делаете со своими данными, применяете к ним функции, вызываете методы. И если вы передали не тот тип, то на первом же запуске в REPL у вас вылетит ошибка.

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

Не сигнализирует в том случае, если манипуляции с AST используются постоянно для всего. Тот же time совершенно не обязательно реализовывать макросом.

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

«Удобство» и «сложность» в любом случае субъективны.

Отнюдь. В программировании есть сложность алгоритмов, которая сводится к количеству шагов, которые нужно сделать. В теории информации сложность выражается черех энтропию. Есть ещё сложность по Колмогорову. В дизайне интерфейсов сложность прекрасно оценивается количественно через A/B тестирование, время на изучение инструментов, количество кликов до достижения цели и т.д. Но вы никаких метрик не приводите, так что да, у вас сложность субъективная.

В том, что ни какая функция в JS не меняет порядок вычисления своих аргументов — все аргументы вычислятся до вызова. Если в вызове есть лямбда, то при чтении кода она видна.

В JavaScript можно явно передать лямбду в функцию и вычислить её внутри или оставить как есть.
В Lisp можно (явно) передать выражение в макрос и вычислить его внутри или оставить как есть.
Найдтите 10 отличий.

Можно пример такой сигнатуры из которой будет понятно как устанавливать соотношения между значением во внешнем цикле и диапазоном во вложенном?

Этот пример вы сами придумали, на практике никто такой ерундой в макросах не занимается. Например, макрос, на который я ссылался, делает N циклов от 1 до максимального индекса текущей размерности, чтобы можно было гибко обрабатывать 2-х, 3-х и 4-хмерные массивы (чёрное-белое 2D, цветное 2D и цветное 3D изображение соответсвенно).

Но, если вы хотите увидеть именно такой макрос, как описали, то он может выглядеть так:

(macro nloops [r f1 f2 & fs] ...)
"""
Generate nested loops.
r - range or collection, constituting top level of loops
f1, f2, fs - function to get current range from a higher-level loop variable, e.g. (fn [i] (range 1 i))
"""

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

Меня интересуют не выходные операции, а то, что на вход. Список функций clojure.

А вы знаете много операций над матрицами?

То есть мы пишем некий код и надеемся, что он как-то будет оптимизирован? Это хоть как-то допустимо для оптимизаций, работающих неявно. Но мы-то говорим о макросе, вызываемом явно! Человек сам его вызывает и надеется на какой-то результат. И проверить результат он может только эмпирически. Так почему бы результат macroexpand не вставить вместо кода, просто чтобы гарантировать, что ни какие последующие изменения, кажущиеся незначительными, не угробят оптимизацию? В этом, кстати, разница с макросом для XPath — он работает детерминировано, такой «оптимизатор» работает «как-то». В следующей версии может заработать по другому. А тесты на производительность крайне трудозатратны.

Простите, а опции компилятору вы передаёте неявно?

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

Ооо, хвостовая рекурсия в Scala — это вообще фантастическая вещь. Начнём с того, что проверять, раскрутилась ли рекурсия в цикл можно чуть ли не в любом языке, который её поддерживает. Scheme хвостовую рекурсию тупо ганартирует — это в стандарте прописано. Clojure для хвостовой рекурсии использует ключевое слово `recur`, которое сразу кинет ошибку, если вы попробуете вызвать его не в хвостовой позиции.

В Scala вы хвостовую рекурсию гарантируете через `@tailrec`. Только вот применить эту аннотацию можно исключительно к приватным или финальным методам, что делает невозможным, например, эти методы потом замокать. Так что пример не сильно удачный.

Что касается преобразований BLAS функций, то они, как правило, достаточно простые. Т.е. в 90% случаев вы будете точно знать, какой код сгенерируется на выходе. А если хочется гарантий, то всё то же самое можно написать и вручную.

Если к какому-то вашему утверждению про сложность можно добавить «это не мнение, это факт», то я не знаю как вообще можно обсуждать субъективные понятия.

А я нигде и не утверждал, что какая-то фича является сложной и её использования нужно избегать.

Если вы считаете, что удобность синтаксиса и простота работы с макросами — это не ваше личное мнение, а факт, то дискуссию закрыть просто необходимо.

Я считаю, что к любому синтаксису можно достаточно быстро привыкнуть и перестать комплексовать. И это уже факт, подтвердждённый вполне немаленьким Lisp сообществом.
Но вы никаких метрик не приводите, так что да, у вас сложность субъективная.

Как и у вас.

В Lisp можно (явно) передать выражение в макрос

Сами отличие поймете? Если нет, то на этом точно стоит окончить этот разговор.
method1(incrementAndGet())
method2(function() { return incrementAndGet(); })

(method1 (incrementAndGet))
(method2 (incrementAndGet))


А я нигде и не утверждал, что какая-то фича является сложной и её использования нужно избегать.

«Простое», «понятное» и «удобное» — тоже утверждения о степени сложности. Или по вашему утверждения о высокой сложности чем-то отличаются от утверждений о низкой сложности в плане субъективности?

Я считаю, что к любому синтаксису можно достаточно быстро привыкнуть и перестать комплексовать.

Человек — он как таракан: ко всему привыкает. Вопрос в том стоит ли оно того.
Про размер сообщества: у меня для вас плохие новости. И у scala и у clojure сообщества микроскопические.
Но вы никаких метрик не приводите, так что да, у вас сложность субъективная.

Как и у вас.

Ну почему же, я вам ещё где-то в самом начале сказал, что ни у кого ни с макросами, ни со скобками не возникает проблем. И многие другие участники этого треда вам об этом сказали. Это статистика людей, которые с этим работали. Вы, судя по всему, с Лиспом работали весьма недолго, а с макросами так и того меньше, статистики такой не имеете и во всех своих суждениях опираетесь на собственные ощущения.

Сами отличие поймете? Если нет, то на этом точно стоит окончить этот разговор.

А, так проблема в том, что вы не можете отличить применение макроса от применения функции, а не в порядке вычисления? Ну тут дело вкуса. Лично я никогда не видел проблемы в том, чтобы их различить (впрочем, как и необходимости их различать): макросы-обёртки почти всегда начинаются со слова `with-`, макросы-определения — с `def`, весь поток контроля — это либо макросы, либо специальные формы. Не всем нравится такой подход, поэтому в некоторых других языках макросы специально каким-то образом выделяются (например, через `@` в Julia), но это уже вопрос стиля. В конце концов, вы всегда можете договориться о конвенции для имён макросов, как это сделано с предикатами (`pred?`) и мутабельными функциями (`change-var!`) в Clojure.

ни у кого ни с макросами, ни со скобками не возникает проблем

Опрос в интернете показал, что 100% опрошенных пользуются интернетом.
Среди clojure сообщества естественно таких проблем нет.
Но в этом треде я был не единственным, кому макросы и скобки создают проблемы со сложностью кода.
Кстати, скобки — не такой уж существенный минус на мой взгляд.

И многие другие участники этого треда вам об этом сказали.

И вам тоже не я один говорил про сложность, вносимую макросами.

с Лиспом работали весьма недолго

Да.

с макросами так и того меньше

Нет.

статистики такой не имеете и во всех своих суждениях опираетесь на собственные ощущения

Не только. А вы свою статистику набирали по опытным лисперам? Или по тем, кто с лиспом только столкнулся?
Я видел разную реакцию на Lisp, но, к сожалению, люди в основном поверхностно о нем судят по скобочкам, так что свою статистику восприятия макросов набирал по сообществу scala.

В конце концов, вы всегда можете договориться о конвенции для имён макросов

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

Да, при этом, что неудивительно, проблемы возникают именно у людей, которые Лиспом в своей жизни (почти) не пользовались.

с макросами так и того меньше


Нет.

С макросами в Лиспе, разумеется. Про вашу статью с макросами на Scala я помню, да.

Не только. А вы свою статистику набирали по опытным лисперам? Или по тем, кто с лиспом только столкнулся?

Кто только столкнулся. Собственно, вы можете быстро собрать статистику сами, например, из вопросов на Stackoverflow. Я сейчас просмотрел первые 4 страницы результатов по запросу «clojure», но не нашёл ни одного вопроса по использованию или даже написанию макросов (что немного сложней). Было пара вопросов, почему в ядре Clojure макросы написаны именно так, о том, как переопределить специальные формы языка с помощью макросов, об отличиях некоторых макросов Clojure от похожих конструкций в других языках, но никто ни разу не спросил «как это работает?» или «как это написать?». Да и вообще, вопросов, хоть как-то связанных с макросами, совсем немного.

Можно, правда, сказать, что понять макросы легко, а вот пользоваться ими тяжело. Ну ок, возьмём запрос "[clojure] macro error" и отсортируем по дате — 5 результатов с начала лета. Для сравнения, запрос "[scala] implicit error" выдаёт 12 результатов только за последнюю неделю.

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

Да, при этом, что неудивительно, проблемы возникают именно у людей, которые Лиспом в своей жизни (почти) не пользовались.

Вы причину со следствием не путаете?
например, из вопросов на Stackoverflow. Я сейчас просмотрел первые 4 страницы результатов по запросу «clojure», но не нашёл ни одного вопроса по использованию или даже написанию макросов

Я посчитал Stackoverflow не репрезентативным по clojure, когда пытался прикинуть размер комьюнити. 10к вопросов по clojure — маловато по сравнению с 40к вопросами по scala при примерно одинаковой дате первого вопроса, это слишком отличалось от моей субъективной оценки соотношения.

никто ни разу не спросил «как это работает?» или «как это написать?»

Гм… Открываю запрос [clojure] defmacro. Первый вопрос по обоим тегам scala и clojure и я сходу не понял что человек хочет. Второй вопрос, 12 часов назад: Making a macro nested in macros using variable arguments work. Что это, если не «как это написать?»? Это, кстати, четвертый вопрос по тегу [clojure].

Я сейчас просмотрел первые 4 страницы результатов по запросу «clojure»

У вас сколько вопросов на страницу настроено? У меня 50. Мне терпения хватило на 100 вопросов по scala (рекомендую искать [clojure] — это тег, а не любое замыкание). Нашел 2 вопроса, напрямую связанные с имплиситами: стоимость преобразований java коллекций. Затруднения тут не прослеживаются, человек просто хочет узнать алгоритмическую сложность метода. И второй. Тут да, коллизия имен имплиситов.

но не нашёл ни одного вопроса по использованию или даже написанию макросов (что немного сложней).

Может их действительно немного, но четвертый вопрос в теге [clojure] — про написание макросов.

запрос "[scala] implicit error" выдаёт 12 результатов только за последнюю неделю.

implicit — ключевое слово в scala, причем часто используемое. Поищите "[scala] class error" — найдете вдвое больше. У людей проблемы с OOP?

Но чтоб не быть голословным я прошелся по первым 50 "[scala] implicit error". 2 вопроса про implicit (1 из них упомянут выше). Еще 2 вопроса про implicit в контексте макросов, один из них вида «у меня всё работает, но почему именно так?».

Вся ваша «статистика» такая бесполезная?
Вы причину со следствием не путаете?

Я сегодня объяснял человеку за 60, который пользуется телефоном раз в неделю, как пользоваться списком контактов. Значит ли это, что список контактов — это сложно?

Вы можете сказать, что телефон и Лисп сравнивать нельзя, ведь телефон есть у 90% населения, а на Лиспе пишут единицы. Но на прошлой неделе я объяснял товарищу, за которого обычно стирает жена, как пользоваться стиральной машиной. Значит ли это, что интерфейс стиральной машины — это сложно?

Если вы чем-то не пользуетесь и не изучаете, то у вас нет шанса узнать и оценить это. Поэтому, конечно, продолжайте ломать копья, доказывая, что Лисп — это сложно, вместо того, чтобы просто взять и изучить инструмент.

Я посчитал Stackoverflow не репрезентативным по clojure

Предложите другую выборку.

Первый вопрос по обоим тегам scala и clojure и я сходу не понял что человек хочет.

Человек хочет работать с фьючерами в Clojure так же, как работал в Scala, хотя принцип работы у них сильно разный. Про макросы в вопросе ни слова.

Второй вопрос, 12 часов назад: Making a macro nested in macros using variable
arguments work. Что это, если не «как это написать?»?

Простите, забыл написать очевидное: вопросы по конкретным и сложным макросам, конечно, встречаются. Точно так же, как и по конкретным и сложным функциям. Или функции — это тоже сложно?

рекомендую искать [clojure] — это тег, а не любое замыкание

Замыкание пишется через «s».

implicit — ключевое слово в scala, причем часто используемое. Поищите "[scala] class error" — найдете вдвое больше. У людей проблемы с OOP?

Ну замените слово «implicit» на тег "[implicit]", а чтобы было честно, в запросе на Clojure тоже добавьте «macro» — соотношение не сильно изменится.

Еще 2 вопроса про implicit в контексте макросов, один из них вида «у меня всё работает, но почему именно так?».

Вот это вообще эпично.
Я сегодня объяснял

Мне не надо объяснять как пользоваться лиспом. Я целенаправленно достаточно глубоко его изучил, чтобы сравнить с другими инструментами и сделать выбор.
Да, опыт снимает проблемы со скобками.
Нет, опыт не добавляет ему плюсов по сравнению с другими языками.

Предложите другую выборку.

Да меня-то устраивает — думал, что вас не устроит. Лисперов действительно так мало?

вопросы по конкретным и сложным макросам, конечно, встречаются

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

Замыкание пишется через «s».

Упс. В любом случае [clojure] — 10к, clojure — 31к мусор 3/1. В случае со scala невозможно искать не по тегу — видимо мусора не так много.

Ну замените слово «implicit» на тег "[implicit]", а чтобы было честно, в запросе на Clojure тоже добавьте «macro» — соотношение не сильно изменится.

А вы пробовали?
[scala] [implicit] — 414, вопросов по clojure в 4 раза меньше, значит вопросов по [clojure] [macros] должно быть меньше сотни. Но их 328!
Если человек задает вопрос, значит у него уже проблемы. Добавление error ничего не менияет по сути. Но ок, добавьте error, получите 259 vs 95. Только не забудьте отнормировать на соотношение количества вопросов 4/1.

Вот это вообще эпично.

Ну вы же отмели
Было пара вопросов, почему в ядре Clojure макросы написаны именно так

Я целенаправленно достаточно глубоко его изучил, чтобы сравнить с другими инструментами и сделать выбор.

«Достаточно глубоко» в вашем понимании подразумевает написание макросов?

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

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

Лисперов действительно так мало?

Не надо путать сэмпл со всей популяцией. Сэмпл используется для оценки внутренних метрик популяции, но не её размера.

Если человек задает вопрос, значит у него уже проблемы. Добавление error ничего не менияет по сути.

«Какая концентрация азота оптимальна для центральной ячейки турбины ракетного двигателя?» — сразу видно, у человека проблемы с рокетными двигателями. Просто не может понять, как ими пользоваться.

Вот это вообще эпично.

Ну вы же отмели
Было пара вопросов, почему в ядре Clojure макросы написаны именно так

Вопрос: почему в ядре Clojure макросы пишутся через `list`, а не через квотирование?
Ответ: потому что ядро разворачивается сверху вниз, и квторирование появляется позже.
О да, сразу видно, что у человека проблемы с пониманием механизма макросов.
А вот фраза «всё работает, но я не понимаю как» — это реально эпично. Причём зная некоторые библиотеки Scala, я вполне понимаю автора этого вопроса.

Но ок, добавьте error, получите 259 vs 95. Только не забудьте отнормировать на соотношение количества вопросов 4/1.

Даже при таком раскладе получается, что макросы в Clojure чуть сложнее имплистов в Scala. Значит ли это, что Scala — почти такой же тяжёлый язык и его стоит избегать.
Не могу не заметить, что мы с вами так и не нашли вопросов по базовому использованию макросов в Лиспе, хотя вы утверждали, что это ад и садомия, и отпугивает всех потенциальных лисперов.
«Достаточно глубоко» в вашем понимании подразумевает написание макросов?

В общем случае нет, но тут уж случайно получилось, что заинтересовало и немного писал.

Про плюсы Clojure я написал ещё в одном из первых комментариев

Можно ссылку? Я, кажется, уже писал, что все плюсы съедаются привнесенной сложностью макросов. Но может какой-то из плюсов упустил.

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

Я и ранее писал, что скобки — поверхностный минус и если к ним привыкнуть минусом они быть перестают.
Но привносимая макросами сложность минусом быть не перестает.

А вот фраза «всё работает, но я не понимаю как» — это реально эпично.

Не переиначивайте. Там вопрос «все работает с оберткой, но почему не работает без нее?».
У меня, кстати, вопрос в том же духе был, когда я изучал scala: «почему работает с lasy val, но не работает с val» — не считаю его фееричным.

Даже при таком раскладе получается, что макросы в Clojure чуть сложнее имплистов в Scala.

Еще раз попытаюсь объяснить свою мысль: макросы привносят сложность. Отдельный макрос в общем случае не сложно написать, не сложно использовать и не сложно читать код с ним. Но каждый макрос привносит дополнительную сложность в поддержку кода. И эта сложность накапливается.
Но отдельный макрос не создает проблем, достаточных для вопроса на SO. Возьмем числа «414 / 40к» и «328 / 10к» — о чем они говорят? Только о том, что проблемы с отдельными макросами и отдельными имплиситами составляют ничтожную долю возникающих проблем. И сами концепции не вызывают особых вопросов.

Кстати, имплиситы тоже привносят накапливающуюся сложность. И в scala применение имплиситов ограничивают. Например запретили implicit def без доп. импорта, который в бизнес логике обычно не используется, оставив только гораздо более простой для понимания implicit class.

В обоих случаях эта сложность — цена за что-то. Это, например, цена за упрощение в другом месте. Если мы берем библиотеку, то она привносит свой DSL. Он может быть основан на макросах, на имплиситах, на чем-то еще. Главное, что он требует изучения, он привносит этим сложность, убирая ее в другом месте.

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

Имплиситы действительно привносят накапливаемую сложность, потому в проектах ограничивают их использование.
Обычно сами используют только implicit class — он имеет аналогии во многих ЯП и не привносит по сути сложности для большенства разработчиков. Остальное — только обдуманно.

Пример библиотеки, кишащей имплиситами — shapeless. Ее используют только для написания других библиотек. Лично я ни разу не слышал о ее использовании напрямую.
Еще библиотека с имплиситами: scalaz. Ее подключают только если вся команда считает ее очевидной и простой. В моем случае это было не так — ее в основной проект тоже не подключали.
Скринг, кстати, написан при помощи shapeless, на для использования выставляет достаточно ограниченный набор имплиситов и не выставляет части shapeless напрямую.

Да, исплиситы создают накапливаемую сложность, которую не оценить по SO (так как каждая часть этой сложности мала). И макросы создают накапливаемую сложность.

Но в scala эта сложность контролируема и инструменты стараются ее не повышать. Фактически при правильном выборе библиотек в большинстве случаев мы получаем только implicit class, а не tipeclass или другие, более сложные концепции.
В clojure же, как я это увидел, никто не испытывает ни малейших комплексов по поводу макросов. Это считается настолько же удобным, как функции. Нельзя просто взять и ограничить эту сложность, не лишив себя вообще всех сторонних инструментов.
Скринг

Spray. Перепутал название и опечатался одновременно.
Да, исплиситы создают накапливаемую сложность, которую не оценить по SO (так как каждая часть этой сложности мала). И макросы создают накапливаемую сложность.

И, конечно же, вы можете поделиться историями из практики? Вы ведь говорите, что даже писали макросы.

Имплиситы действительно привносят накапливаемую сложность, потому в проектах ограничивают их использование.
[...]
В clojure же, как я это увидел, никто не испытывает ни малейших комплексов по поводу макросов. Это считается настолько же удобным, как функции.

И какой из этого можно сделать вывод? ;)

Можно ссылку? Я, кажется, уже писал, что все плюсы съедаются привнесенной сложностью макросов. Но может какой-то из плюсов упустил.

habrahabr.ru/post/264005/#comment_8522727
Если отбросить макросистему и общие со Scala плюс вроде доступа к библиотекам JVM, немутабельность и т.п., то я бы выделил такие плюсы Clojure:

  • Богатая коллекция персистентных коллекций. Одни вектора чего стоят!
  • Лаконичность. Программы на Clojure, как правило, гораздо короче аналогов на Scala и Java
  • Простота. Обычно, чтобы начать работать с новой библиотекой, нужно потратить на чтение документации примерно 2 минуты. Сам язык, кстати, тоже очень небольшой, практически без advanced features, которые понимают единицы
  • Инструменты сборки проекта, а именно Leiningen. Я уже недавно где-то в комментариях сравнивал Maven, SBT и Leiningen, и почему только последним из них я действительно доволен
  • Одна парадигма. Та же Scala берёт корни как минимум в Java, Ruby, Haskell и Erlang, что иногда делает сложным комбинировать библиотеки, написанные в разных стилях
  • Ну и всякие мелкие вещи типа multiple dispatch по любому условию (а не только по классу), ленивые коллекции, унифицированные интерфейсы и т.д.

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

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

доступа к библиотекам JVM

Проблема в том, что совместимость однонаправленная. Чем-то из clojure уже в Java не воспользоваться. Это мне в groovy в свое время не понравилось.

немутабельность

Не так плохо, что (def name ...) мутабелен, как то, что еще и скоупов нет.
Богатая коллекция персистентных коллекций. Одни вектора чего стоят!

Вы же выкинули общее с scala? Вектора, если мне память не изменяет, утянуты как раз с clojure.
Но там еще много вкусного, например ленивые и параллельные коллекции.

Лаконичность. Программы на Clojure, как правило, гораздо короче аналогов на Scala и Java

По поводу scala сомневаюсь. Разве что на типы, но это не существенно. Надеюсь вы не будете апеллировать к своему примеру кода на Spray. =)

Простота.

Вот к этому все мои рассуждения про сложность, привносимую макросами.

Сам язык, кстати, тоже очень небольшой

Clojure не имеет смысла рассматривать без стандартного набора макросов, с которым придется столкнуться. От чего язык резко перестает быть «небольшим».

Инструменты сборки проекта, а именно Leiningen.

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

Та же Scala берёт корни как минимум в Java, Ruby, Haskell и Erlang, что иногда делает сложным комбинировать библиотеки, написанные в разных стилях

ООП + ФП. От руби я увидел только влияние RoR на Play, но это не относится к самому языку. От Erlang — только влияние на акторы, которых в стандартной библиотеке уже нет. В любом случае акторы — самый удобный для меня подход к многопоточности и называть их отсутствие плюсом я бы не стал.
Если уж брать влияние на либы, то еще Haskell сильно повлиял. И этому влиянию я рад.
У меня проблем со комбинированием библиотек не было, но тут уж кому как повезло, наверное.

multiple dispatch по любому условию

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

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

Эх, значит примером, как обычно, не будет…
Про Scala, кстати, я могу рассказать достаточно много заметных историй, начиная с того самого имлисит таймаута.

Проблема в том, что совместимость однонаправленная. Чем-то из clojure уже в Java не воспользоваться

Да что вы говорите! Для примера, ядро Apache Storm написано на Clojure с интерфесами на других JVM языках.

Не так плохо, что (def name ...) мутабелен

Мутабелен в том смысле, что он добавляет новое определение в пространство имён? Ну с таким подходом даже Haskell весь мутабельный.

как то, что еще и скоупов нет

Ой, вот лучше здесь остановитесь, если не разбираетесь. `def` по спецификации объявляет глобальную переменную, локальные скоупы (для переменных или функций) вводятся специальной формой `let`:

(let [my-local-func (fn [x] (+ x 1))]
   (my-local-func 42))


Вот к этому все мои рассуждения про сложность, привносимую макросами.

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

Clojure не имеет смысла рассматривать без стандартного набора макросов

Вообще-то, с точки зрения дизайна языка, очень даже имеет. И тут Лиспы рвут практически все другие языки со своим минимальным набором примитивов. С точки зрения практики набор основных макросов и специальных форм тоже совсем небольшой, остальные банально выводятся из них (например, зная `if` и `let`, наверное несложно догадаться, что делает `if-let`).

Пробовал. По началу хотелось кого-нибудь придушить

Вот это даже интересно, а расскажите, почему?

ООП + ФП.

Да, это официальная линия партии, пропагандируемая товарищем Одерски. На практике же:

1. Половина программ написана в императивном стиле, Scala используется просто как Java на стеройдах. Пример — Apache Spark (гайд от databricks в этом смысле вообще интересно читать).
2. Последний тренд — реактивные, а совсем не функциональные программы. Причём тренд настолько сильный, что уже повлиял на название компании Typesafe (я думаю, вы в курсе, так что ссылку искать не буду).

От руби я увидел только влияние RoR на Play, но это не относится к самому языку.

А как же свободный синтаксис, который позволяет писать, например, `time {… }` вместо `time({ => ...})`?

Лично мне pattern matching гораздо больше нравится, особенно учитывая возможность кастомных матчеров (по любому условию) и проверку компилятором на покрытие всех вариантов.

Плюс макросистемы Лиспа как раз в том, что если вам чего-то в языке не хватает, вы всегда можете дописать это сами! Кстати, сделать Haskell-like паттерн матчинг, т.е. когда не так:

def func(x) = x match {
  case foo => ... 
  case bar => ...
}


а так

func foo = ...
func bar = ...

С помощью макросов в Clojure тоже не составляет труда.

Предлагаю на этой позитивной ноте окончим данный не конструктивный диалог.

Меня, в общем-то, интересует только чем вам не понравился Leiningen, а в остальном я не против.
Меня, в общем-то, интересует только чем вам не понравился Leiningen, а в остальном я не против.

После этого стер все, что было выше. Пора уже завязывать спорить о вкусе фломастеров.
По Leiningen ничего серьезного. Небольшие проблемы с установкой (теперь это единственная программа, которая у меня не под контролем пакетного менеджера) и проблемы с lein repl — не правильно реагировал на стрелки и Home/End по началу. Не серьезно, но нервы потрепало.
Мутабелен в том смысле, что он добавляет новое определение в пространство имён? Ну с таким подходом даже Haskell весь мутабельный.


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

Ну да: «смотрите какой ужасный код на Scala и как миленько то же самое на Clojure». При том, что код на scala ужасен только из-за автора, но не инструмента.
Ну да: «смотрите какой ужасный код на Scala и как миленько то же самое на Clojure». При том, что код на scala ужасен только из-за автора, но не инструмента.

Ок, если вам не даёт покоя этот пример, давайте вернёмся к нему. Напомню, что речь шла о том, что с макросами код становится запутанным, на что я привёл пример:

def objectExitss: Route = get {
    pathPrefix(ApiVersion) {
      pathPrefix("group" / IntNumber) { gId =>
        path("objects" / IntNumber) { objId =>
          traceName(Traces.ObjectExists) { ctx =>
            // handler code goes here
          }
        }
      }
    }
  }

Этот код не регистрирует новый ендпоинт, а только конструирует объект типа `Route`. Только вот метод верхнего уровня — `get` — возвращает не `Route`, а `Directive0`. Дальше идут `pathPrefix` и `path`, которые внезапно делают одно и то же — матчат путь, но при этом `pathPrefix` перекрывает по функционалу `path`.

Что возвращают `path` и `pathPrefix`? Конечно же, по этому куску кода вы поняли, что они возвращают `Directive`! Не `Directive0`, как метод `get`, а именно `Directive`. Окей, теперь всё понятно, каждая вложенная функция должна возвращать какую-нибудь `Directive`. Ан-нет, код хэндлера может быть любым и никому ничего не должен.

Ну да ладно, этот код, конечно же, ужасен из-за его авторов, а не из за фреймворка. Возьмём ваш код:

def objectExitss = get { path(ApiVersion / "group" / IntNumber / "objects" / IntNumber) { (groupId, objId) =>
  <handler code goes here>
}}

Что это за `Directive`, `Route` и т.д. (а «интересных» понятий в Spray придостаточно, моё любимое — rejections) мы так и не поняли, но чёрт с ним. Зато в аргументе `path` мы делим строчку на строчку на какую-то константу на строчку на константу. Океееей.

Ну да ладно, к неявным преобразованиям и перегрузке операторов в Scala мы уже привыкли, хотя что именно происходит непонятно. Но, по крайней мере, синтаксис мы поняли — `get { path { } } `. Или нет?

val route: Route = {
  (path("search") & get) {
    // handler code
  }
}


What the ***?!

А если ещё добавить опциональные параметры:

val route: Route = {
  (path("search") & get) {
    parameters("q"!, "filter" ? "all") { (query, filter) => 
      ...
    }
  }
}

То вообще красота.

Это не значит, что на Scala нельзя сделать простой и понятный фреймворк — Finatra прекрасно показывает, что можно — но после примера со Spray (любого из приведённых примеров и практически любого, который вы найдёте в интернете) говорить о том, что макросы в Clojure делают код запутанным — это уже моветон.
Зато в аргументе `path` мы делим строчку на строчку на какую-то константу на строчку на константу. Океееей.

Цитируя вас:
Не придумывайте, даже вы, жалуясь на непонятность, на самом деле сразу догадались


What the ***?!

Directive — единица роутинга — условие, накладываемое на запрос. get — условие на метод, path — условие на url, parameters — условие на CGI параметры. Уверен, что там есть еще условия на тело, на куки. Можно написать кастомные условия на сессию, на IP отправителя, на что хотите.

pathPrefix — условие на часть url, path — частный случай pathPrefix, условие на весь url.

Авторы посчитали логичным комбинировать условия через &.

path(«search») & get

Лично я бы скомбинировал в обратном порядке, но всё в руках пишущего.

  (path("search") & get) {
    parameters("q"!, "filter" ? "all") { (query, filter) => 


можно вот так написать:
(get & path("search") & parameters("q"!, "filter" ? "all")) { (query, filter) => 

Просто авторы оставили возможность комбинировать директивы не только через &, но и иерархически, позволяя выделять главные и второстепенные.

Вообще, почему я, впервые увидев такую работу с директивами в спринге и имея полтора часа опыта общения со спрингом, объясняю многоопытному собеседнику идеологию спринга? Я впервые это вижу и отлично понимаю предназначение инструментов. Воистину: красота — в глазах смотрящего.

понятный фреймворк — Finatra

Каждый выбирает по себе… Мне финатра не слишком понравилась, но использовать и ее можно.

но после примера со Spray (...) говорить о том, что макросы в Clojure делают код запутанным — это уже моветон.

Допустим спрей ужасен сам по себе (я так не считаю, но для рассуждений это не существенно): как существование плохого инструмента может сделать другой инструмент лучше? Ладно бы спрей был общепризнанным эталоном, но нет, он довольно ограничено используется. Ширпотребом в scala-web является Play.
Directive — единица роутинга — условие, накладываемое на запрос. get — условие на метод, path — условие на url, parameters — условие на CGI параметры. Уверен, что там есть еще условия на тело, на куки. Можно написать кастомные условия на сессию, на IP отправителя, на что хотите.

Не совсем так: директивы пытаются отфильтровать запросы, проверяя на соответсвие с образцом. При этом, если запрос совпадает с образцом, то совпашкая часть «потребляется» директивой (т.е. `pathPrefix(«foo») & pathPrefix(«bar»)` и `pathPrefix(«bar») & pathPrefix(«foo»)` — это неэквивалентные фильтры, ибо будут «съедать» ), а если не совпадает, то вызывается ещё дополнительный механизм под названием rejections, который накапливает неподошедшие запросы и затем может сформировать http ответ.

Цитируя вас:

Так я, собственно, и не говорил, что тема неподъёмная. Всё можно понять и ко всему привыкнуть, и лисповские макросы — это далеко не самая страшная вещь.
При этом, если запрос совпадает с образцом, то совпашкая часть «потребляется» директивой

Очень удивился, прочитав это. Для pathPrefix такое поведение было очевидно (как и то, что директивы кроме проверки еще и вытаскивают значения — потому и DIrectiveN), но для остальных — нет.
Сейчас пробежался по коду — нет, таким развлекается только pathPrefix, это не общая черта директив. Даже parameters не выкусывает параметры.

Ну а про механизм rejections — это вариация стандартного для некоторого класса scala-библиотек механизм ValidationNel, пришедшего из Haskell. Наверное удобно для отладки, но для разработки они его сделали достаточно незаметным.
>IDE же для динамически типизированных языков довольно бесполезен.

Такого идиотизма я давно не слышал. Вы SLIME или Smalltalk видели хоть в глаза?
Такого идиотизма я давно не слышал.

А я смотрю патологическая вежливость — отличительная черта lisp-сообщества.

Я видел Ruby, Python, JS, Perl и, внезапно, Clojure. Во всех этих языках IDE беспомощнее слепого котенка и способна только форматирование поправить. Предел мечтаний тут — vim с автоподстановкой по словарю.
Такая примитивная операция, как переименование чего-либо во всем проекте, становится невозможной при наличии омонимов.

Не знаю, может в «SLIME или Smalltalk» есть какая-то магия — мне это не очень важно. Во всех динамически типизированных языках, с которыми у меня есть шанс столкнуться, IDE — просто очень тяжелый текстовый редактор.
Я видел Ruby, Python, JS, Perl и, внезапно, Clojure. Во всех этих языках IDE беспомощнее слепого котенка и способна только форматирование поправить.


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

Такая примитивная операция, как переименование чего-либо


Рефакторинг это лишь одна из многих возможностей IDE.

Во всех динамически типизированных языках, с которыми у меня есть шанс столкнуться


Если не изучать — шанс не будет увеличиваться, если шанс не будет увеличиваться то зачем изучать?
Рефакторинг это лишь одна из многих возможностей IDE.

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

Если не изучать — шанс не будет увеличиваться, если шанс не будет увеличиваться то зачем изучать?

Будем реалистами: с Haskell и то шанс больше на порядки.
Smalltalk у меня в планах на изучение как источник идей для многих других языков, но точно не как нечто, на чем я планирую писать когда-либо. Примерно как Io — один раз попробовал, понравилось, закрыл навсегда.
Еще есть intellisense

Не знаю что это.

Smalltalk у меня в планах на изучение как источник идей для многих других языков, но точно не как нечто, на чем я планирую писать когда-либо. Примерно как Io — один раз попробовал, понравилось, закрыл навсегда.

Common Lisp тут нечто среднее, в нем множество возможностей для изучения, идей и тд, но в то же время он черезвычайно практичен. Да, вы можете найти нужную библиотеку с последними изменениями лет 5-8 назад, но по своему опыту скажу что она скорее всего отлично работает, а если не отлично — минимальных правок будет достаточно (при чем вы даже можете переопределить символы из библиотеки внутри своего кода).

Мне, например, пришлось поправить библиотеку парсинга rss чтобы она пропускала теги Atom, формата, который просто появился позже чем была написана эта библиотека =)
Ну вот, например pycharm не смотря на то, что ни разу не решарпер, но и не блокнот.
Конечно-конечно. В нормальные языки для этого добавляют неявные преобразования типов, ленивые вычисления, неявные параметры. Зачем обходится одними макросами, если можно добавить это все и еще немножко, попутно усложнив компилятор и увеличив порог вхождения в язык.
В коде выше макросы действительно не очень осмысленны во всех языках, кроме Lisp-подобных.
Там даже не убрано дублирование имен переменных, так что пишется это и в C# и в Java и в любом другом языке без всего перечисленного вами примерно так:
Routes.create("myapp")
  .get("/v1/group/:groupId/objects/:objId", (groupId, objId) -> { <handler code goes here> })
  .post(...)

Да, придется повозиться с перегрузкой по количеству параметров. Хотя в любом динамически типизированном (вроде JS) даже с перегрузкой заморачиваться не придется.

Та же функция time или автозакрытие ресурсов во всех языках с лямбдами (C#, Java, JS, etc) пишется тривиально.

Если мы захотим убрать дублирование имен переменных, заодно проконтролировав их тип автоматически, то тут нам уже понадобится подключать scala-макросы:
val router = Router.from {
  case GET(p"/hello/$to") => Action {
    Ok(s"Hello $to")
  }
}

Кстати, на лиспе ничто не мешало так сделать (убрать [groupId objId]), но тогда макрос, начав делать что-то полезное, перестанет быть таким маленьким и красивым.

И еще раз: «нормальные» я тут использовал как синоним «обычные», не предполагая негативного оттенка.
Да, я уверен что defroutes макрос.
Макросы нужны как раз когда нужен новый язык


Необязательно, макросы в общем случае это функция типа Ast -> Ast, «новый язык» это смысл, вкладываемый конкрентым человеком.
Кстати да, я даже, кажется, знаю почему у вас такой scala код.

Там скорее всего несколько десятков методов, сгруппированных.

В 1 файл закинули весь роутинг. сначала роутятся по ApiVersion, потому обрабатывают несколько вариантов, один из которых «group» / gId, затем уже в group несколько вариантов, один из которых «objects» / objId.

Вы же потерли всё, кроме 1 метода.

Не знаю стоило ли так делать — я бы разбил по нескольким файлам или как минимум вынес бы проверку ApiVersion отдельно — это другой уровень абстракции и смешивать не стоило.
Кстати да, я даже, кажется, знаю почему у вас такой scala код.

Нет, не из-за этого даже. Насколько я помню, мы то ли трейсили разные части пути по разному, то ли пытались побороться с этой манерой Спрея заставлять делать пути вида `/group//objects` вместо простого `/`, но в итоге пришли именно к такому виду. Т.е. для нашего совсем не сложного юз кейса такой накрученный вариант был рекоммендованным подходом.
то ли трейсили разные части пути по разному

В clojure это бы привело к не меньшей каше, так ведь?

заставлять делать пути вида `/group//objects` вместо простого `/`

Насколько я себе это представляю, сделать такое вполне возможно с тем синтаксисом, что я показал.

для нашего совсем не сложного юз кейса

Сформулируйте юзкейс и я покажу как это сделать красиво. Желательно еще красивое решение на clojure, реализующее этот юзкейс со всеми требованиями, чтоб убедиться, что красивое решение вообще возможно.
Мне кажется, вы пытаетесь доказать, что на Scala можно писать красивый код. Спасибо, я и так в курсе. Если вы проследите ветвь дискуссии до моего изначального комментария, то увидите, что в нём я не пытался доказать некрасивость кода на Scala, а наоборт, пытался показать, что макросы в Lisp ничем не хуже, а то и лучше. Т.е. если вы всё ещё отвечаете на тот комментарий, то доказывать нужно как раз то, что на Clojure чего-то сделать нельзя или это будет выглядеть некрасиво. Но поверьте, этого сделать вам не удастся.

Если же вы давно забыли про тот комментарий и просто ищете хороший из кейс, который можно красиво сделать на Clojure, но не на Scala, то попробуйте трейсить запросы без вмешательства в код хэндлера. Т.е. как в том же моём комментарии, но без необходимости каждый раз вызывать `traceName {...}`. В Clojure это решается элементарным переопределением макроса `defroutes`. Что-то вроде:

(defmacro my-defroutes [name & routes]
   (let [new-routes (map trace-name routes)]
      (defroutes name new-routes)))


Естественно, вместо использования нового имени можно просто перекрыть старый `defroutes` и использовать свой «прокачанный» макрос как drop-in replacement для старого кода.
я не пытался доказать некрасивость кода на Scala, а наоборт, пытался показать, что макросы в Lisp ничем не хуже, а то и лучше

Только зачем-то приводите кусок кода, изуродованный неумением пользоваться спреем. Который годится только для создания ложного впечатления о коде на Scala.

Естественно, вместо использования нового имени можно просто перекрыть старый `defroutes` и использовать свой «прокачанный» макрос как drop-in replacement для старого кода.

Ну определите свой метод myPath вместо path. Очень хочется назвать его path? Так всегда пожалуйста — это тоже можно сделать при помощи cake pattern. `with NameTracing`. Самое забавное, что такой подход стекуется: `with NameTracing with OtherTracing`.
Зачем тут вообще могут понадобиться макросы? Что в лиспе мешает определить функцию?
(defn my-defroutes [name & routes]
  (let [new-routes (map trace-name routes)]
      (defroutes name new-routes)))

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

С функциями придется точно также придется учить какие функции что делают, какие аргументы принимают и возвращают, какие побочные эффекты имеют и тд. Тут у вас в голове может возникнуть аргумент типа «но макросы сложнее понимать» — отвечу что не намного сложнее чем функции, просто у вас, наверняка, за плечами несколько лет практики чтения кода состоящего из функций, но нет аналогичного опыта с макросами.

Все это детсад и фантазерство.

И про функции высших порядков так говорили, и про проверку корректности типами, и про многое другое)
>И про функции высших порядков так говорили, и про проверку корректности типами, и про многое другое)

Так это пока экзотикой и остается.
«как нет ещё ни у кого» — вообще-то лиспов (языков с таким синтаксисом и достаточно разной семантикой) вагон и маленькая тележка. Уж за оригинальность синтаксиса разработчики Clojure не боролись.
Чтобы не использовать польскую нотацию, можно использовать макрос ->>.
Мой опыт с clojure ограничивается всего лишь одно программой на 900 строк, но что-то продолжать его не хочется, слишком неудобно.
А если скрестить кложуру с ~кофескриптом, чтоб вместо кучи скобок использовать табуляцию. Я даже уверен что кто-то это уже делал.
Конечно, изначально это и был план. Потом народ кучу раз пытался заменить S-выражения на что-то свое — сделать «свой лисп, но без скобочек». Только не приживается идея.
На самом деле проблема скобочек очень сильно преувеличена. Обычно их не намного больше, чем в аналоличном C-like коде.
На много, если учесть что даже в арифметике на каждое действие надо как минимум пару скобок. Расширения к редакторам в стиле Rainbow Parenthesis на ровном месте не рождаются. И даже с ними иногда понимание не 100% что во что вложено.
Да. На арифметику больше скобочек надобно. Но иногда столько же (или даже меньше).
if (boolVar)
    method1();
else
    method2();

против
(if boolVar
  (method1)
  (mothod2))

А если еще вспомнить, что во многих код-стайлах надо обрамлять одинарные операторы в фигурные скобки…
Прикол в том, что в си-подобных языках скобочки-то разные. И обрамлять в них надо только блоки, а не каждый оператор или функцию.А тут один вид практически для всего. В вашем примере в конце всего 2 скобки, а в нетривиальных их может быть и 10, а может быть где-нибудь в середине. Короче наглядность у этого способа явно не на высоте.
Справедливости ради надо заметить, что в Clojure скобочки тоже разные.
При этом их немного меньше, чем в CL или Scheme — вместо

(let ((x 1)
      (y 2))
  (+ x y))

тут

(let [x 1
      y 2] 
  (+ x y))


Ну это понятно, список, вектор и хэш — три вида скобок. Но список чаще всего используется. В то время как в си-подобных языках скобки нужны только в особых случаях, вроде группировки арифметических или логических операций, а также выделение блоков кода, в лисп-подобных скобками обрамлено буквально все.
В то время как в си-подобных языках скобки нужны только в особых случаях, вроде группировки арифметических или логических операций
А еще для вызова функций. Вот уж действительно «особый случай».
Не понял, для какого вызова? Для передачи аргументов что ли? Логично вроде.
Ну вот пишете, мол
в си-подобных языках скобки нужны только в особых случаях
При этом скобки нужны на каждый вызов функции, на каждый if, for, while и т.п.
Это явно не особые случаи.
Начать хотя бы с того, что это как раз таки не функции, а операторы. Потому что в классических си-подобных языках блок кода не может быть аргументом. А вообще сильно зависит от уровня синтаксического сахара в самом языке. В Perl например скобки в таких случаях не всегда нужны.
Я включил «rainbow parenthesis» в кложуре, очень помогло. А потом включил их и в кофескрипте, и кофескрипт стал ещё няшнее.
Извиняюсь за картинку вне спойлера.
Я не спец, но, мне кажется неправильным делать статьи вроде «Переход из ПХП в Лисп».

Тут ведь должно меняться мышление, эти языки не только синтаксисом отличаются.
Предалагаю все примеры на Кложуре оформлять с радужными скобками. Если ваш редактор не поддерживает такие скобки — вы можете использовать подсветку кода в КС: github.com/venantius/glow
Вообще, я бы их везде использовал, где уместно. Примеры в картинках:
картинки
Coffee


Clojure


C#

Sign up to leave a comment.

Articles