モナドでのHaskellWikiの再帰には、末尾再帰であると主張されている例があります。
f 0 acc = return (reverse acc)
f n acc = do
v <- getLine
f (n-1) (v : acc)
命令型の表記法は、末尾再帰であると私たちに信じさせますが、それは(少なくとも私には)それほど明白ではありません。砂糖do
を取り除くと
f 0 acc = return (reverse acc)
f n acc = getLine >>= \v -> f (n-1) (v : acc)
2行目を書き直すと
f n acc = (>>=) getLine (\v -> f (n-1) (v : acc))
したがって、これは末尾再帰の位置ではなく、f
の2番目の引数の内側で発生することがわかります。答えを得るには、>>=
を調べる必要がIO
あります。>>=
明らかに、ブロックの最後の行として再帰呼び出しを使用することはdo
、関数が末尾再帰であるための十分な条件ではありません。
このモナドのすべての再帰関数が次のように定義されている場合、モナドは末尾再帰であるとしましょう。
f = do
...
f ...
または同等に
f ... = (...) >>= \x -> f ...
末尾再帰です。私の質問は:
- 末尾再帰のモナドは何ですか?
- 末尾再帰モナドをすぐに区別するために使用できる一般的なルールはありますか?
更新:具体的な反例を作成しましょう:[]
上記の定義によれば、モナドは末尾再帰ではありません。もしそうなら、
f 0 acc = acc
f n acc = do
r <- acc
f (n - 1) (map (r +) acc)
末尾再帰である必要があります。ただし、2行目の脱糖は
f n acc = acc >>= \r -> f (n - 1) (map (r +) acc)
= (flip concatMap) acc (\r -> f (n - 1) (map (r +) acc))
明らかに、これは末尾再帰ではなく、IMHOを作成することはできません。その理由は、再帰呼び出しが計算の終わりではないためです。これは数回実行され、結果が組み合わされて最終結果が作成されます。