Reputation: 16116
Why are some methods in Scala's standard libraries implemented with mutable state?
For instance, the find
method as part of scala.Iterator
class is implemented as
def find(p: A => Boolean): Option[A] = {
var res: Option[A] = None
while (res.isEmpty && hasNext) {
val e = next()
if (p(e)) res = Some(e)
}
res
}
Which could have been implemented as a @tailrec
'd method, perhaps something like
def findNew(p: A => Boolean): Option[A] = {
@tailrec
def findRec(e: A): Option[A] = {
if (p(e)) Some(e)
else {
if (hasNext) findRec(next())
else None
}
}
if (hasNext) findRec(next())
else None
}
Now I suppose one argument could be the use of mutable state and a while
loop could be more efficient, which is understandably very important in library code, but is that really the case over a @tailrec
'd method?
Upvotes: 2
Views: 280
Reputation: 167891
Scala is not designed for functional purity but for broadly useful capability. Part of this includes trying to have the most efficient implementations of basic library routines (certainly not universally true, but it often is).
As such, if you have two possible interfaces:
trait Iterator[A] { def next: A }
trait FunctionalIterator[A] { def next: (A, FunctionalIterator[A]) }
and the second one is awkward and slower, it's quite sensible to choose the first.
When a functionally pure implementation is superior for the bulk of use cases, you'll typically find the functionally pure one.
And when it comes to simply using a while
loop vs. recursion, either one is easy enough to maintain so it's really up to the preferences of the coder. Note that find
would have to be marked final
in the tailrec
case, so while
preserves more flexibility:
trait Foo {
def next: Int
def foo: Int = {
var a = next
while (a < 0) a = next
a
}
}
defined trait Foo
trait Bar {
def next: Int
@tailrec def bar: Int = {
val a = next
if (a < 0) bar else a
}
}
<console>:10: error: could not optimize @tailrec annotated method bar:
it is neither private nor final so can be overridden
@tailrec def bar: Int = {
^
There are ways to get around this (nested methods, final, redirect to private method, etc.), but it tends to adds boilerplate to the point where the while
is syntactically more compact.
Upvotes: 0
Reputation: 32335
In this case the tailrec
quite possibly has the same performance as the while
loop. I would say that in this case the while
loop solution is shorter and more concise.
But, iterators are a mutable abstraction anyway, so the gain of having a tail recursive method to avoid that var
, which is local to that short code snippet, is questionable.
Upvotes: 3
Reputation: 9862
There is no harm in having a mutable state as long as he is not shared.
In your example there is no way the mutable var could be accessed from outside, so it's not possible that this mutable variable change due to a side effect.
It's always good to enforce immutability as much as possible, but when performance matter there is nothing wrong in having some mutability as long as it's constrained in a safe way.
NOTE: Iterator is a data-structure which is not side-effect free and this could lead to some weird behavior, but this is an other story and in no way the reason for designing a method in such way. You'll find method like that in immutable data-structure too.
Upvotes: 4