Lai Yu-Hsuan
Lai Yu-Hsuan

Reputation: 28071

Built-in way to simulate a while loop without side effects

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

Answers (2)

senia
senia

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.

Iterator.continually

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.

Tail recursion

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

Scalaz unfold

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

Rob Napier
Rob Napier

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

Related Questions