#purescript
Вопрос:
Если actions
это достаточно большой массив, это создает переполнение стека:
modifyPerIndex :: forall a. Array (Tuple Int (a -> a)) -> Array a -> Array a
modifyPerIndex actions array = run do
mutableArray <- thaw array
for_ actions (Tuple index action) -> modify index action mutableArray
pure mutableArray
Это не означает:
modifyPerIndex :: forall a. Array (Tuple Int (a -> a)) -> Array a -> Array a
modifyPerIndex actions array = run do
mutableArray <- thaw array
foreach actions (Tuple index action) -> void $ modify index action mutableArray
pure mutableArray
Я предполагаю, что именно поэтому Контроль.Монада.ST имеет свою собственную версию foreach. for_
«s Applicative
amp; Foldable
» — гораздо более слабое ограничение, чем foreach
«s Array
«. Тем не менее, я не уверен, чего Applicative
amp; Foldable
не хватает, что for_
не может быть безопасным для стека (или не может быть без какого-либо другого недостатка)
Я немного покопался в источнике и отметил, что for_
это реализовано с помощью foldr
. Я не уверен, где найти экземпляр папки массива.
Я довольно новичок во многом из этого, просто пытаюсь расширить свое общее понимание. 🙂
Ответ №1:
Foldable
Экземпляр для Array
находится здесь, и вот фактический код для foldr
. Как вы можете видеть, он безопасен для стека, потому что он вообще не использует стек: это просто обычный старый цикл JS, который изменяет аккумулятор.
Что в конечном итоге оказывается небезопасным для стека traverse_
(что for_
связано с перевернутыми аргументами). Проверьте источник: видите, что функция складывания есть (*>) f
? Это означает, что результатом выполнения traverse_
некоторых Foldable
из них будет серия *>
вызовов, что-то вроде этого:
traverse_ f [1,2,3,4] == f 1 *> f 2 *> f 3 *> f 4 *> pure unit
Ключевым моментом здесь является то, что f
вычисления на самом деле не выполняются во traverse_
время выполнения. traverse_
просто строит цепочку из них таким образом, и только когда вы идете и bind
это (либо с >>=
a, либо внутри a do
) — вот когда эта цепочка выполняется.
И что происходит, когда вы пытаетесь запустить вычисление, построенное на *>
? Ну, *>
это псевдоним для applySecond
, который сам использует <*>
— псевдоним для apply
, который является методом Apply
, чей экземпляр для ST
использования ap
, который, в свою очередь, опирается на монадическую привязку.
Тело ap
привязывает первое вычисление, которое есть f 1 *> f 2 *> f 3 *> f 4
, но это не конечный вызов, поэтому оно отправляется в стек. В свою очередь, привязка этого вычисления приводит к попытке привязать его собственную левую часть, которая f 1 *> f 2 *> f 3
и так далее, вплоть до f 1
. Так что стопка взрывается.
traverse_
(и for_
) на самом деле не может сделать ничего лучше, потому что они оба чистые, поэтому они не могут запускать эффекты, поэтому все, что они могут сделать, это создать цепочку из них в надежде, что кто-то другой выполнит ее позже.
И в этом заключается ответ: почему бы специальной версии traverse_
не знать, как запускать эффекты?
И вот: Traversable
!
Это класс типа , несколько похожий на Foldable
, но со встроенными эффектами. В принципе, он может складываться по последовательности, но функция складывания эффективна.
Это позволяет запускать серию эффектов, не разрушая стек, но есть и другая проблема: вы не можете игнорировать конечный результат. Если у вас есть массив со 100 тысячами элементов для начала, в конце вы получите еще один. К счастью, это не такая большая проблема, как взорвать стопку.
Комментарии:
1. Спасибо за ответ и ссылки. Теперь это имеет для меня смысл в общих чертах. Я углублюсь в это немного подробнее, чтобы убедиться, что я действительно понимаю 🙂