2019-01-11 PureScript で何気なくスタックオーバーフローした
PureScript で何気なく↓を実行した。
main = for_ (0 .. 10000) logShow
↓は結果。
RangeError: Maximum call stack size exceeded
スタックオーバーフロー。
for_
なんて名前だけどループのつもりで使うと死ぬ。そこで原因を調べていく。 for_
は traverse_
を flip
したもの。
traverse_
は foldr
している。
traverse_ f = foldr ((*>) <<< f) (pure unit)
どうも Effect
を大量にまとめているせいかな。
今回に限っての簡単な解決策は purescript/purescript-effect の foreachE
。
main = foreachE (0 .. 100000) logShow
https://pursuit.purescript.org/packages/purescript-effect/2.0.0/docs/Effect#v:foreachE
JavaScript の for
で Effect
を呼び出す形に変換される。もし Array
の各要素に Effect Unit
を……という話ならこれで良さそう。 Effect
と同じパッケージに入っているのでまず間違いなくあるのも良い。
ほかには paf31/purescript-safely の for_
が良さそう。これなら見た目はまったく変わらない。
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
クラスを直接使う。つまり tailRec
や tailRecM
などを使う。
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 関連のツイートは本当に限られた人しかしないのでもうだいたいフォローしてしまっている) 。
ゲームだとループ頻出 & 自力で末尾再帰にする能力ない、で愛用してるライブラリ。
— ヘンゼルの記憶 (@hansel_no_kioku) January 11, 2019
特に Aff が MonadRec のインスタンスなの超重要♪https://t.co/N4GLHhtk1t
おしまい。
`main = for_ (0 .. 10000) logShow`
— bouzuya (@bouzuya) 2019年1月9日