Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Isn't that code in tail recursive style?

I'm kinda new to Scala trying it out while reading Beggining Scala by David Pollack. He defines a simple recursive function that loads all strings from the file:

def allStrings(expr: => String): List[String] = expr match {     case null => Nil     case w => w :: allStrings(expr) } 

It's elegant and awesome except that it had thrown a StackOverflow exception when I tried to load a huge dictionary file.

Now as far as I understand Scala supports tail recursion, so that function call couldn't possibly overflow the stack, probably compiler doesn't recognize it? So after some googling I tried @tailrec annotation to help the compiler out, but it said

error: could not optimize @tailrec annotated method: it contains a recursive call not in tail position def allStrings(expr: => String): List[String] = 

Am I understanding tail recursion wrong? How do I fix this code?

like image 599
Grozz Avatar asked May 14 '11 23:05

Grozz


People also ask

How do you know if a tail is recursive?

A function is tail-recursive if it ends by returning the value of the recursive call. Keeping the caller's frame on stack is a waste of memory because there's nothing left to do once the recursive call returns its value.

What is a tail-recursive function example?

A recursive function is tail recursive when a recursive call is the last thing executed by the function. For example the following C++ function print() is tail recursive.

What is tail recursion in programming?

(algorithmic technique) Definition: A special form of recursion where the last operation of a function is a recursive call. The recursion may be optimized away by executing the call in the current stack frame and returning its result rather than creating a new stack frame.


1 Answers

Scala can only optimise this if the last call is a call to the method itself.

Well, the last call is not to allStrings, it's actually to the :: (cons) method.

A way to make this tail recursive is to add an accumulator parameter, for example:

def allStrings(expr: => String, acc: List[String] = Nil): List[String] =   expr match {     case null => acc     case w => allStrings(expr, w :: acc)   } 

To prevent the accumulator leaking into the API, you can define the tail recursive method as a nested method:

def allStrings(expr: => String) = {   def iter(expr: => String, acc: List[String]): List[String] =     expr match {       case null => acc       case w => iter(expr, w :: acc)     }   iter(expr, Nil) } 
like image 71
Ben James Avatar answered Nov 09 '22 14:11

Ben James