blog.bouzuya.net

2019-01-11 PureScript で何気なくスタックオーバーフローした

PureScript で何気なく↓を実行した。

main = for_ (0 .. 10000) logShow

↓は結果。

RangeError: Maximum call stack size exceeded

スタックオーバーフロー。

for_ なんて名前だけどループのつもりで使うと死ぬ。そこで原因を調べていく。 for_traverse_flip したもの。

https://pursuit.purescript.org/packages/purescript-foldable-traversable/4.1.1/docs/Data.Foldable#v:for_

traverse_foldr している。

traverse_ f = foldr ((*>) <<< f) (pure unit)

https://github.com/purescript/purescript-foldable-traversable/blob/v4.1.1/src/Data/Foldable.purs#L198

どうも Effect を大量にまとめているせいかな。

今回に限っての簡単な解決策は purescript/purescript-effectforeachE

main = foreachE (0 .. 100000) logShow

https://pursuit.purescript.org/packages/purescript-effect/2.0.0/docs/Effect#v:foreachE

JavaScript の forEffect を呼び出す形に変換される。もし Array の各要素に Effect Unit を……という話ならこれで良さそう。 Effect と同じパッケージに入っているのでまず間違いなくあるのも良い。

ほかには paf31/purescript-safelyfor_ が良さそう。これなら見た目はまったく変わらない。

main = for_ (0 .. 100000) logShow

https://pursuit.purescript.org/packages/purescript-safely/4.0.0/docs/Control.Safely#v:for_

これは MonadRec を使って実装された for_ などを含むパッケージ。 MonadRec は末尾再帰の際にスタックを大量に消費しないことを表す型クラス。 tailRecM を提供する。

ただし package-sets の最新のタグ (psc-0.12.1-20190107) に safely パッケージは含まれていない。 psc-package を使う場合には注意。

https://github.com/purescript/package-sets/tree/psc-0.12.1-20190107

最後の方法は↑にも書いた MonadRec クラスを直接使う。つまり tailRectailRecM などを使う。

https://pursuit.purescript.org/packages/purescript-tailrec/4.0.0/docs/Control.Monad.Rec.Class#t:MonadRec https://pursuit.purescript.org/packages/purescript-tailrec/4.0.0/docs/Control.Monad.Rec.Class#v:tailRec

Aff なども MonadRec のインスタンスになっているらしい (関係ないけど PureScript 関連のツイートは本当に限られた人しかしないのでもうだいたいフォローしてしまっている) 。

おしまい。