Во-первых, более простой MCVE:
(def fs (repeat 1e6 identity))
((apply comp fs) 99)
#<StackOverflowError...
Но почему это происходит?Если вы посмотрите на (сокращенный) comp
источник:
(defn comp
([f g]
(fn
([x] (f (g x)))
([f g & fs]
(reduce1 comp (list* f g fs))))
Вы можете увидеть, что все это в основном всего 2 части:
Первый аргументперегрузка, которая выполняет основную работу , оборачивая каждый составной вызов функции в другую функцию .
Сокращение над функциями с использованием comp
.
Я считаю, что первый вопрос - это проблема.comp
работает, беря список функций и постоянно упаковывая каждый набор вызовов в функции.В конце концов, это приведет к исчерпанию стекового пространства, если вы попытаетесь создать слишком много функций, так как в конечном итоге создаст массивную функцию, охватывающую многие другие функции.
Итак, почему ТШО не может помочь здесь?Поскольку в отличие от большинства StackOverflowErrors, рекурсия здесь не является проблемой.Рекурсивные вызовы только когда-либо достигают одного кадра глубоко в вариационном случае внизу.Проблема заключается в создании массивной функции, которую нельзя просто оптимизировать.
Почему вы смогли это исправить?Поскольку у вас есть доступ к val
, вы можете оценивать функции по ходу работы, а не создавать одну функцию для последующего вызова.comp
был написан с использованием простой реализации, которая отлично работает в большинстве случаев, но не работает в крайних случаях, таких как тот, который вы представили.Хотя написать специализированную версию, которая обрабатывает огромные коллекции, довольно тривиально:
(defn safe-comp [& fs]
(fn [value]
(reduce (fn [acc f]
(f acc))
value
(reverse fs))))
Конечно, обратите внимание, что она не обрабатывает несколько массивов, как в основной версии.
Честно говоря,за 3 года использования Clojure я ни разу не написал (apply comp ...)
.Хотя, безусловно, возможно, что вы столкнулись с делом, с которым мне никогда не приходилось иметь дело, более вероятно, что вы используете не тот инструмент для этой работы.Когда этот код будет завершен, опубликуйте его в Code Review, и мы сможем предложить лучшие способы выполнения того, что вы пытаетесь сделать.