如何解决Purescript 的 Data.Foldable for_ 不是堆栈安全的吗?
如果 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
我假设这就是为什么 Control.Monad.ST 有自己的 foreach 版本。 for_
的 applicative
& Foldable
是比 foreach
的 Array
宽松得多的约束。话虽如此,我不确定 applicative
和 Foldable
缺少什么,以至于 for_
不能是堆栈安全的(或者不能没有其他一些缺点)
我对源代码进行了一些挖掘,并注意到 for_
是通过 foldr
实现的。我不确定在哪里可以找到 Array 的 foldr 实例。
我对这方面的很多东西都很陌生,只是想扩大我的一般理解。 :)
解决方法
Foldable
的 Array
实例是 here,而 here 是 foldr
的实际代码。正如你所看到的,它是堆栈安全的,因为它根本不使用堆栈:它只是一个普通的旧 JS 循环,它改变了一个累加器。
最终不是堆栈安全的是traverse_
(它是带有翻转参数的for_
)。查看the source:看到折叠函数是(*>) f
吗?这意味着在某些 traverse_
上运行 Foldable
的结果将是一系列 *>
调用,如下所示:
traverse_ f [1,2,3,4] == f 1 *> f 2 *> f 3 *> f 4 *> pure unit
此处的关键见解是 f
计算实际上并未在 traverse_
的执行期间运行。 traverse_
只是像这样构建一个它们的链,并且只有当你去 bind
它(使用 >>=
或在 do
中)时 - 那就是该链被执行的时候.
当您尝试运行基于 *>
的计算时会发生什么?好吧,*>
是 applySecond
的别名,它本身利用了 <*>
- apply
的别名,它是 Apply
的一个方法,其 {{3 }} 使用 instance for ST
。
ap
的主体绑定了第一个计算,即 f 1 *> f 2 *> f 3 *> f 4
,但这不是尾调用,因此它进入堆栈。反过来,绑定该计算会导致尝试绑定它自己的左侧部分,即 f 1 *> f 2 *> f 3
,依此类推,一直到 f 1
。所以堆栈炸了。
traverse_
(和for_
)真的不能做得更好,因为它们都是纯的,所以它们不能运行效果,所以它们所能做的就是建立一个链他们希望其他人稍后会执行它。
答案就在于此:为什么不使用确实知道如何运行效果的特殊版本的 traverse_
?
看哪:ap
,which in turn relies on monadic bind!
这是一个有点类似于 Foldable
的类型类,但具有内置的效果。基本上可以折叠一个序列,但是折叠功能是有效的。
这允许在不破坏堆栈的情况下运行一系列效果,但有一个不同的问题:您不能忽略最终结果。如果您有一个包含 100K 个元素的数组开始,您将在最后得到另一个。谢天谢地,这不是一个大问题。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。