Reputation: 145
I have a following problem.
class NonEmpty(elem: Tweet, left: TweetSet, right: TweetSet) extends TweetSet {
@tailrec final def filterAcc(p: Tweet => Boolean, acc: TweetSet): TweetSet =
if(p(elem))
(this remove elem).filterAcc(p, acc incl elem)
else
(this remove elem).filterAcc(p, acc)
}
Scala tels me, that it can not optimize @tairec, because my method contains a recursive call targeting a supertype. (Superclass of NonEmpty is class TweetSet, where the method filterAcc is defined). How to deal with such an error?
Upvotes: 1
Views: 462
Reputation: 170713
In this case it may make sense to move filterAcc
into the companion object:
trait TweetSet {
final def filterAcc(p: Tweet => Boolean) = TweetSet.filterAcc(p, this, Empty)
}
object TweetSet {
@tailrec
private def filterAcc(p: Tweet => Boolean, self: TweetSet, acc: TweetSet) = self match {
case ne: NonEmpty =>
// the original body with `this` replaced by `self` and made an argument for recursive calls
val elem = ne.elem
if(p(elem))
filterAcc(p, self remove elem, acc incl elem)
else
filterAcc(p, self remove elem, acc)
// other cases
}
}
Upvotes: 0
Reputation: 2900
This is intended behavior. The error occurs due to how @tailrec
works: in essence, the compiler tries to transform a recursive call into a local loop. In spite of the fact that your method is recursive, it recurses by calling itself on another instance not even of the same type, but of the supertype - and since other children of TweetSet
may provide a completely different implementation of filterAcc
, the compiler cannot make sure this method can be safely expanded into a loop.
A rule of thumb for tail-recursive functions is to ensure that it always calls exactly itself as the last statement - in your case, instead of calling
(this remove elem).filterAcc(...)
you have to try and transform acc
in the way that emulates this remove elem
and call
filterAcc(p, <transformed acc>)
In functional programming involving recursion it is a good idea to define the entire method in the superclass (in your case TweetSet
) and rely on pattern matching by ADT members instead of polymorphism - that way there will be no issues with optimizing recursion.
Upd: There's a nice blog post explaining tail recursion, as specified in an answer to this question
Upvotes: 4