Under what circumstances are monadic computations tail-recursive?

若如初见. 提交于 2019-12-03 04:27:13

A monadic computation that refers to itself is never tail-recursive. However, in Haskell you have laziness and corecursion, and that is what counts. Let's use this simple example:

forever :: (Monad m) => m a -> m b
forever c' = let c = c' >> c in c

Such a computation runs in constant space if and only if (>>) is nonstrict in its second argument. This is really very similar to lists and repeat:

repeat :: a -> [a]
repeat x = let xs = x : xs in xs

Since the (:) constructor is nonstrict in its second argument this works and the list can be traversed, because you have a finite weak-head normal form (WHNF). As long as the consumer (for example a list fold) only ever asks for the WHNF this works and runs in constant space.

The consumer in the case of forever is whatever interprets the monadic computation. If the monad is [], then (>>) is non-strict in its second argument, when its first argument is the empty list. So forever [] will result in [], while forever [1] will diverge. In the case of the IO monad the interpreter is the very run-time system itself, and there you can think of (>>) being always non-strict in its second argument.

What really matters is constant stack space. Your first example is tail recursive modulo cons, thanks to the laziness.

The (getLine >>=) will be executed and will evaporate, leaving us again with the call to f. What matters is, this happens in a constant number of steps - there's no thunk build-up.

Your second example,

f 0 acc = acc
f n acc = concat [ f (n - 1) $ map (r +) acc | r <- acc]

will be only linear (in n) in its thunk build-up, as the result list is accessed from the left (again due to the laziness, as concat is non-strict). If it is consumed at the head it can run in O(1) space (not counting the linear space thunk, f(0), f(1), ..., f(n-1) at the left edge ).

Much worse would be

f n acc = concat [ f (n-1) $ map (r +) $ f (n-1) acc | r <- acc]

or in do-notation,

f n acc = do
  r <- acc
  f (n-1) $ map (r+) $ f (n-1) acc

because there is extra forcing due to information dependency. Similarly, if the bind for a given monad were a strict operation.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!