Ответ 1
То, что вы видите, является неприятным побочным эффектом оптимизации, которая была внедрена в реализацию макроса doseq
для обработки фрагментированных последовательностей на входе. Ответ на вопрос, который вы правильно связали, описывает основную причину, но не проливает много света на то, почему все происходит так, как они делают.
В реализации doseq
внутренне используется функция, которая рекурсивно создает серию вложенных конструкций loop
, один loop
для каждого уровня привязок в doseq
. В наивной, неоптимизированной версии этой реализации петля на каждом уровне просто запускает свое тело, а затем вызывает recur
со значением next
для своего seq. Что-то в этом роде:
(loop [s (seq input)]
(if s
(do (run-body (first s))
(recur (next s)))))
Если этот seq является чередующейся последовательностью, это приведет к ненужному созданию множества промежуточных объектов seq, которые никогда не используются вне тела цикла. Оптизация, которую doseq
сделала, состоит в том, чтобы поместить if
внутри loop
с одной ветвью для обработки последовательностей с чередованием, а один - для обработки непересекающихся последовательностей. Тело цикла дублируется между каждой ветвью. Если тело цикла является вложенным циклом, то вы можете увидеть, как происходит экспоненциальное увеличение размера кода - цикл на каждом уровне расширенного кода имеет два дочерних цикла.
Итак, чтобы ответить на ваш вопрос, я бы точно не сказал, что взрыв в размере кода предназначен, но это следствие разработанного поведения doseq
. Он просто не предназначен для обработки глубоко вложенных циклов, и в дикой природе я никогда не видел, чтобы он использовался с более чем одним или двумя уровнями привязок.
Вы можете воспроизвести семантику глубоко вложенного doseq
с комбинацией for
и dorun
(не использовать doall
, поскольку это без необходимости сохраняет главу seq). Это позволит вам обрабатывать любой уровень вложенности с небольшой, но измеримой производительностью, если вы выполняете чередующуюся последовательность в узком цикле.
user> (time (doseq [x (range 10000) y (range 10000)] (* x y)))
"Elapsed time: 2933.543178 msecs"
user> (time (dorun (for [x (range 10000) y (range 10000)] (* x y))))
"Elapsed time: 5560.90003 msecs"