TL;DR Defining functions "tail-recursively" is not generally useful in lazy languages. You can get a stack overflow even in those cases (as opposed to strict languages with a proper tail-call optimization).
By using lazy evaluation of the function parameters (call by name) you lose the ability to express a simple iteration (using constant space) with a recursion.
For example let's compare two versions of length function. First let's look at the non-tail recursive function to compare lazy and non-lazy:
length [] = 0
length (head:tail) = length tail + 1
Strict evaluation of length [1, 2, 3]
:
length [1, 2, 3] ->
length (1:[2, 3]) = length [2, 3] + 1 ->
length (2:[3]) + 1 = (length [3] + 1) + 1 ->
(length (3:[]) + 1) + 1 = ((length [] + 1) + 1) + 1 ->
((length [] + 1) + 1) + 1 = ((0 + 1) + 1) + 1 ->
(1 + 1) + 1 ->
2 + 1
3
Lazy evaluation:
length [1, 2, 3] ->
length (1:[2, 3]) = length [2, 3] + 1 ->
at this point reducing the +
is needed and it needs to evaluate both arguments, the first being length [2, 3]
, so it continues like before, but on a list with one less parameter.
Stack space is used for evaluating +
(if we are considering a straightforward implementation).
Both versions thus use a stack, for the lazy one though, instead of length
, +
is the "recursive" function here.
Tail-recursive variant (using an accumulator):
length [] a = a
length (head:tail) a = length tail (a + 1)
Strict evaluation steps utilizing tail-call optimization:
length [1, 2, 3] 0 ->
length (1:[2, 3]) 0 = length [2, 3] (0 + 1) ->
length (2:[3]) 1 = length [3] (1 + 1) ->
length (3:[]) 2 = length [] 2 + 1 ->
length [] 3 = 3
This uses a constant space on a stack, no space on a heap
Lazy evaluation:
length [1, 2, 3] 0 ->
length (1:[2, 3]) 0 = length [2, 3] (0 + 1) ->
length (2:[3]) (0 + 1) = length [3] ((0 + 1) + 1) ->
length (3:[]) ((0 + 1) + 1) = length [] ((0 + 1) + 1) + 1 ->
length [] (0 + 1) + 1) + 1 = ((0 + 1) + 1) + 1 ->
magic -> 3
This uses a constant space on a stack until "magic" part, but builds-up delayed computations (additions) on a heap (generally). The part marked "magic" is where the sum of all ones happen and in a straightforward implementation it uses a stack. (Note that in this and similar cases, the optimizing evaluator may actually do the additions during the evaluation and then just return 3, you cannot really tell the difference, but the stack is not used. It can use other tricks to prevent a stack overflow like CPS).
Summary:
Lazy evaluated functions use a stack in the end in its straightforward implementation regardless whether they are written as tail-recursive or not.
There are however various tricks you need to apply to compilers to enable the optimizations of stack usage. They are not as straightforward as tail-call optimization in strict languages though.
Better option is to use those languages idiomatically and utilize the variety of high order functions that significantly reduce a need for recursive function definitions (and to some degree are in the lazy languages also for that reason).