Я не совсем уверен, какие входы вызывают взрыв вашей функции.Я не могу заставить его взорваться двумя подпоследовательностями по 5 миллиардов элементов:
boot.user=> (defn move-split [[xs ys]]
#_=> (doall
#_=> (concat
#_=> (list (concat xs (list (first ys))))
#_=> (list (next ys)))))
#'boot.user/move-split
boot.user=> (def x (move-split [(range 5e0) (range 5e0)]))
#'boot.user/x
boot.user=> x
((0 1 2 3 4 0) (1 2 3 4))
boot.user=> (def x (move-split [(range 5e9) (range 5e9)]))
#'boot.user/x
boot.user=> (-> (nth x 0) first)
0
boot.user=> (-> (nth x 1) first)
1
Однако есть много возможностей сделать эту функцию более идиоматичной.Глядя на функцию:
(defn move-split [[xs ys]]
(doall
(concat
(list (concat xs (list (first ys))))
(list (next ys)))))
В строке 4: нам не нужно создавать list
, поскольку concat
уже возвращает последовательность.Точно так же нам не нужно создавать list
в строке 5, next
уже возвращает последовательность.С этими двумя изменениями:
(defn move-split [[xs ys]]
(doall
(concat
(concat xs (list (first ys)))
(next ys))))
Теперь, почему в мире мы испортили бы совершенно хорошую (ленивую) функцию, вставив doall
вверху?Давайте избавимся от этого.Кроме того, поскольку функция должна возвращать последовательность из двух элементов, почему бы просто не сделать это вместо вызова concat
в строке 3?Идиоматический способ построения двухэлементной последовательности - через векторный литеральный синтаксис.С этими двумя изменениями мы имеем:
(defn move-split [[xs ys]]
[(concat xs (list (first ys)))(next ys)])
О, и мы можем снова применить векторный литеральный синтаксис вместо этого list
конструктора, что дает нам окончательную версию:
(defn move-split [[xs ys]]
[(concat xs [(first ys)])(next ys)])
Теперь давайте попробуем это fn out ...
boot.user=> (defn move-split [[xs ys]]
#_=> [(concat xs [(first ys)])(rest ys)])
#'boot.user/move-split
boot.user=> [(range 5e0) (range 5e0)]
[(0 1 2 3 4) (0 1 2 3 4)]
boot.user=> (def x (move-split [(range 5e0) (range 5e0)]))
#'boot.user/x
boot.user=> x
[(0 1 2 3 4 0) (1 2 3 4)]
boot.user=> (-> (nth x 0) first)
0
boot.user=> (-> (nth x 1) first)
1
Кажется, это работает.Что если у нас есть две огромные подпоследовательности?Давайте попробуем это с парой подпоследовательностей в 5 миллиардов элементов:
boot.user=> (def x (move-split [(range 5e9) (range 5e9)]))
#'boot.user/x
boot.user=> (-> (nth x 0) first)
0
boot.user=> (-> (nth x 1) first)
1
К вашему первоначальному вопросу, как я уже сказал, я не знаю, какие входные данные вызывали исходную функцию, которая взорвала стек.Я видел это в документации concat
doc :
Конкатенация конкатенаций не выравнивается автоматически!Таким образом, clj :: clojure.core / redux с concat генерирует чрезвычайно вложенные структуры и может легко генерировать переполнения стека при попытке пройти по результирующей последовательности.(применить concat ...) должно быть предпочтительным.
Давайте использовать reduce
to concat
последовательность из 10 последовательностей из 10 целых чисел:
boot.user=> (take 5 (reduce concat [] (for [x (range 1e1)] (range 1e1))))
(0 1 2 3 4)
Но если мыпопробуйте использовать от reduce
до concat
последовательность из 10 000 последовательностей из 10 целых чисел ::
boot.user=> (take 5 (reduce concat [] (for [x (range 1e5)] (range 1e1))))
java.lang.StackOverflowError:
Использование apply
вместо этого работает нормально:
boot.user=> (take 5 (apply concat (for [x (range 1e5)] (range 1e1))))
(0 1 2 3 4)
Просто кое-что сохранитьпри рекурсивном применении concat
.