I’m learning tail recursion and I’m having some difficulties in determining whether my function is tail recursive or not (mainly on functions which I use another function).
I’ve implemented the two following functions but I’m not so sure if they are tail recursive or not.
The first one is a function to concatenate two lists.
conca list [] = list
conca [] result = result
conca (h:t) result = conca (init (h:t)) ( last(h:t):result )
concatenate::[a]->[a]->[a]
concatenate list1 list2 = conca list1 list2
The computations in the function are processed before the recursive call, but its using last and init, which aren’t tail recursive ( I checked their definition in http://ww2.cs.mu.oz.au/172/Haskell/tourofprelude.html )
The second function is to remove the first occurrence of a given number in a given list.
invert [] result = result
invert (h:t) result = invert t (h:result)
remov n [] aux result = invert result []
remov n (h:t) aux result
| aux==1 = remov n t 1 (h:result)
| n==h = remov n t 1 (result)
| otherwise = remov n t 0 (h:result)
remove n list = remov n list 0 []
The parameter aux (which can assume 0 or 1 as value) is being used to mark if the ocurrence has been removed or not.
In the remove function while the partial result is passed down through the recursive call the list is being inverted, upon the end the list is without the first ocurrence but upside-down, thus it have to be inverted to be returned as a result.
is a tail call, but
last(h:t):resultstarts life as an unevaluated thunk, so it’s a (hand-wavy) bit like these nested function calls are on the stack still.concapattern matches on its first argument soinitwill be evaluated in the recursive call.concais non-strict in its second argument, so those thunks won’t get evaluated while applying the recursive call ofconca.removis tail recursive, yes, but….Use
TrueandFalseinstead of0and1to make your code clearer:This would be better not passing around so much data, reducing the copying of
nand using two functions instead of a single function that tests a boolean parameter:but
got a resultjust calculatesreverse result ++ a, so you could writeHowever, it all seems rather a lot of effort, and still traverses the list twice. Why not go for a non-tail call:
This has the benefit of producing its first element straight away rather than running the whole list, and short cuts to return
twithout further computation once it’s found the element to remove. Try racinglength (removeFast 1 [1..100000])againstlength (remove 1 [1..100000])(vary the number of zeros according to your processor speed).If you want to make a more efficient tail recursive
conca, you could use the trick fromremove:As before, you’re traversing
thistwice, onceinverting it, the otherprependingit, but that’s still a linear algorithm, and much better than usinginitandlastfor each element, which is quadratic.