3

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.

1
  • You have no initial value of p. Do you mean do {...} while(p())? Commented Dec 22, 2013 at 5:54

2 Answers 2

5
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
Sign up to request clarification or add additional context in comments.

Comments

1

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

2 Comments

You don't need toSeq here. And you could replace var with val to stress that you don't need vars (but it seems still to take a var to store side effect.).
The 'var's were a typo. I'd just forgotten to switch them to val. As you say, this works fine with vals. Thanks for the note about toSeq; some part of an intermediate design required a Seq and I forgot to remove it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.