1

2 weeks new to haskell and functional programming. In the process of covering foldl and foldr in class, I found that I was quite new to tail recursion and never actually tried writing a tail recursive function before (also new to how foldl traverses a list which is where it came up).

As an exercise, I tried to rewrite the following function to be tail recursive.

--replace replaces all instances of value x in a list with y

replace :: (Eq a) => a -> a -> [a] -> [a]
replace _ _ [] = []
replace x y (h:t) = 
                (if x==h then y: (replace x y t) else h: (replace x y t))

--tail recursive version of the same function
replacet _ _ [] = []
replacet x y (h:t) = 
                    (if x==h then (replacet x y (y:t)) else (replacet x y (h:t)))

...But the output is just the following in ghci:

ghci session

Nothing seems to be running at all, let alone getting an overflow.

Any ideas?

6
  • Have you tried evaluating that call by hand? What are the arguments to replacet in the first recursive call? What are the arguments to replacet in the second recursive call? Commented Jan 29, 2023 at 5:30
  • 3
    And an aside: tail recursion tends not to be particularly relevant in Haskell. Tail call optimization is great for strict languages, where the call stack and the evaluation stack happen to coincide, but in Haskell they tend to be very different. Generally, guarded recursion is the thing to aim for instead. Commented Jan 29, 2023 at 5:37
  • Thanks for the response. By my calculation for replacet 1 2 [1,2] first call: 1==1 is True, calls replacet 1 2 [2,2] second call: 1==2 is False, calls replacet 1 2 [2,2] third call: 1==2 is False, calls replacet 1 2 [2,2] . . and it looks like the third call just goes on and on without hitting the base case. Curious as to why it didn't keep running though (or did it?). Smart compiler? Commented Jan 29, 2023 at 5:51
  • 1
    It did keep running, unless you explicitly killed it. If it had finished, you would have gotten another ghci> prompt to enter your next expression for evaluation. Commented Jan 29, 2023 at 5:53
  • 1
    If you really want to use tail recursion, even if it will lead to a worse program, note that you need a function with an additional "accumulator" parameter e.g. f x y t acc. Here, t is a list which will always decrease at each call, like the original program, while the result is slowly accumulated in acc (which can increase at each call). Initially then replacet x y t = f x y t [] will provide the value for acc. Commented Jan 29, 2023 at 10:10

1 Answer 1

3

Your replacet never makes any progress. Observe that at each step, your input list stays the same size: you replace (h:t) with either (y:t) or (h:t), and then call replacet on the result.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.