Re: stack overflow in tail recursive function

Daniel Fischer <[email protected]> Wed, 24 Mar 2010 11:27:18 +0100 (CET)
Newsgroups gmane.comp.lang.haskell.hugs.user
Message-ID <7340184.936.1269426438583.JavaMail.fmail@mwmweb056>
-----Urspr=C3=BCngliche Nachricht-----
Von: Neil Mitchell <[email protected]>
Gesendet: 23.03.2010 21:40:39
An: Bruno Schneider=20
Betreff: Re: [Hugs-users] stack overflow in tail recursive function

>Hi Bruno,
>
>I suggest you ask this question on the haskell-cafe@ mailing list -
>it's a general Haskell question, and you'll get a much more detailed
>answer there.
>
>Thanks, Neil
>


Yes, this sort of general Haskell questions will receive earlier and more a=
nswers in the cafe.
Nevertheless, Neil, I'm not sure he'd get a much more detailed answer to th=
is question there ;)

Okay, so

>On Tue, Mar 23, 2010 at 12:01 PM, Bruno Schneider  wrote:
>> Hi all,
>>
>> I have this tail recursive factorial function:
>>
>> factorial :: Integer -> Integer
>> factorial 0 =3D 1
>> factorial n =3D fat' n 1 where
>> =C2=A0 =C2=A0fat' 1 fat =3D fat
>> =C2=A0 =C2=A0fat' n fat =3D fat' (n-1) (n*fat)
>>
>> Whenever I run it with a number of 20000 or more I get a stack
>> overflow error. It doesn't seem a problem with the large resulting
>> number because, if so, the message should be something like "Garbage
>> collection fails to reclaim sufficient space". Other functions seem to
>> able to handle a larger number of recursive calls.
>>
>> So, what is the problem with this particular function?


Laziness is more pervasive than you expected. Your accumulator doesn't actu=
ally accumulate the product so far, it accumulates the way to obtain that p=
roduct, because the multiplication isn't carried out but deferred until rea=
lly needed.
So the evaluation of factorial 4 goes

fat' 4 1
(4 /=3D 1)
fat' (4-1) (4*1)
(4-1 =3D 3 /=3D 1)
fat' (3-1) (3*(4*1))
(3-1 =3D 2 /=3D 1)
fat' (2-1) (2*(3*(4*1)))
(2-1 =3D 1 =3D=3D 1)
(2*(3*(4*1)))

and this expression is only evaluated if necessary. factorial 20000 builds =
a thunk of 20000 nested multiplications, this is tried to evaluate when the=
 value is demanded for printing, but the expression is too deeply nested to=
 fit on the stack.

You have to make sure the multiplications in the accumulator aren't deferre=
d until the very end.
Here, the only sensible way is to explicitly tell the Haskell system to eva=
luate the product immediately,

factorial :: Integer -> Integer
factorial n
  | n < 0      =3D error "factorial of negative argument"
  | n =3D=3D 0    =3D 1
  | otherwise =3D fac' n 1
    where
      fac' 1 acc =3D acc
      fac' k acc =3D fac' (k-1) $! k*acc

The ($!) says "evaluate now (to the outermost constructor)", for Integers t=
hat's complete evaluation, so you have no thunks building up and you can ca=
lculate factorials of far larger numbers (though it's slow this way).

A stack overflow most of the time signals a laziness leak, that some part o=
f a function built up a thunk where it shouldn't and you have to force some=
 level of evaluation manually.

>>
>> --
>> Bruno Schneider
>> http://www.dcc.ufla.br/~bruno/
>> _______________________________________________
>> Hugs-Users mailing list
>> [email protected]
>> http://www.haskell.org/mailman/listinfo/hugs-users
>>
>_______________________________________________
>Hugs-Users mailing list
>[email protected]
>http://www.haskell.org/mailman/listinfo/hugs-users