While reading Guido's reasoning for not adding tail recursion elimination to Python, I concocted this example of almost tail recursion in Haskell:
triangle :: Int -> Int
triangle 0 = 0
triangle x = x + triangle (x - 1)
This is of course not a tail call because although the recursive call is in the "return" per se, the x +
prevents the current stack from being reused for the recursive call.
However, one can transform this into code that is tail recursive (albeit rather ugly and verbose):
triangle' :: Int -> Int
triangle' = innerTriangle 0
where innerTriangle acc 0 = acc
innerTriangle acc x = innerTriangle (acc + x) (x - 1)
Here innerTriangle
is tail recursive and is kickstarted by triangle'
. Although trivial, it seems like such a transformation would also work for other tasks such as building a list (here acc
could just be the list being built).
Of course, if a recursive call isn't in the function return, this doesn't seem possible:
someRecusiveAction :: Int -> Bool
someRecursiveAction x = case (someRecursiveAction (x * 2)) of
True -> someAction x
False -> someOtherAction x
But I am only referring to "almost tail" calls, ones where the recursive call is part of the return value but isn't in tail position due to another function application wrapping it (such as the x +
in the triangle
example above).
Is this generalizable in a functional context? What about an imperative one? Can all functions with recursive calls in their returns be transformed into functions with returns in tail position (i.e. ones that can be tail call optimized)?
Never mind the fact that none of these are the "best" way to calculate a triangle number in Haskell, which AFAIK is triangle x = sum [0..n]
. The code is purely a contrived example for this question.
Note: I've read Are there problems that cannot be written using tail recursion?, so I'm fairly confident that the answer to my question is yes. However, the answers mention continuation passing style. Unless I'm misunderstanding CPS, it seems like my transformed triangle'
is still in direct style. In which case, I'm curious about making this transformation generalizable in direct style.