Reputation: 28071
Generally the boilerplate of while loop looks like(r
is the result I want, p
is the predictor:
var r, p;
while(p()) {
(r, p) = compute(r)
}
I can convert it into a recursion to get rid of var
:
def f(r) = {
val (nr, p) = compute(r)
if(p()) nr
else f(nr)
}
Is there a built-in way to implement such logic? I knew Iterator.continually
, but it seems still to take a var
to store side effect.
Upvotes: 3
Views: 608
Reputation: 38045
def compute(i: Int): (Int, () => Boolean) =
(i - 1) -> { () => i > 1 }
To create an immutable while
you'll need iteration
- a function that accepts state
and returns new state
of same type plus exit condition.
It's not the best solution - in my opinion this code is hard to read, but since you mentioned it:
val (r, p) = Iterator.continually(()).
scanLeft( 13 -> { () => true } ){
case ((r, p), _) => compute(r)
}.dropWhile{ case (r, p) => p() }.
next
// r: Int = 0
// p: () => Boolean = <function0>
You could use val (r, _) =
since you don't need p
.
If you want a solution with Iterator
see this answer with Iterator.iterate
.
I guess this is an idiomatic solution. You could always rewrite your while
loop as tail recursion with explicit state type:
@annotation.tailrec
def lastWhile[T](current: T)(f: T => (T, () => Boolean)): T = {
val (r, p) = f(current)
if (p()) lastWhile(r)(f)
else r
}
lastWhile(13){ compute }
// Int = 0
In case you are using scalaz
there is such method already. It produces a Stream
, so you should get the last element.
At the end of an iteration you should produce an Option
(None
is an exit condition) with Pair
of stream element (r
) and next state (r, p())
:
unfold(13 -> true) { case (r0, p0) =>
val (r, p) = compute(r0)
p0.option(r -> (r, p()))
}.last
// Int = 0
Upvotes: 5
Reputation: 299265
I don't know if this really answers the question of "built-in." I don't believe there's a solution that is simpler to implement or understand than your recursive routine. But here's another way of attacking the problem.
You can use Iterator.iterate
to create an infinite iterator, and then find the first element that fails the predicate.
// Count until we are greater than 5
def compute(r: Int): (Int, () => Boolean) = {
(r + 1, () => (r < 5))
}
// Start at the beginning
val r = 1
val p = () => true
// Create an infinite iterator of computations
val it = Iterator.iterate((r, p))({
case (r, _) => compute(r)
})
// Find (optionally) the first element that fails p. Then get() the result.
val result = it.find({ case (_, p) => !p() })
.map { _._1 }
.get
Upvotes: 1