tags:

views:

489

answers:

5

I'd like to convert a piece of Java code which looks like the following into Scala:

for (Iterator<Task> it = tasks.iterator(); it.hasNext() && workflow.isAutoRun();) {
    Task task = it.next();
    if (!runTask(task)) break;
}

I am not a fan of the scala for-comprehensions (not that I know how to break the iteration anyway) and I've come up with the following:

val completed = tasks.forall { task => workflow.isAutoRun && runTask(task) }

However, the scaladoc for the forall method looks like the following (italics mine):

Apply a predicate p to all elements of this iterable object and return true, iff the predicate yields true for all elements

This is not equivalent to what I have done (because it implies that the predicate will be evaluated for each item, regardless of whether a previous evaluation has returned false) and (in fact) not actually equivalent to what the forall method actually does, which on Iterator looks like:

 def forall(p: A => Boolean): Boolean = {
   var res = true
   while (res && hasNext) res = p(next)
   res
 }

Anyway, I digress: has anyone got any better suggestions for what the scala code should look like? I want to see something which conveys the intent better:

tasks.doUntil(t => !isAutoRun || !runTask(t))

+2  A: 

"exists" returns true and terminates the loop once the predicate returns true:

val completed = tasks.exists { task =>
  if (workflow.isAutoRun) {
    runTask(task)
    false
  }
  else
    true
}

Edit:

Try this then:

implicit def enrichList[A](list: List[A]) = new {
  def doWhile(cond: (A) => Boolean)(body: (A) => Unit) = list exists { x =>
    if (cond(x)) {
      body(x)
      false
    }
    else
      true
  }
}

List(1, 2, 3, 4).doWhile(_ <= 3)(println) // prints "1 2 3"

tasks.doWhile(_ => workflow.isAutoRun)(runTask)
Walter Chang
Yes - don't think this is much clearer however - I want to be able to write `tasks.foreach(runTask) while (wf.isAutoRun)` or something like that.
oxbow_lakes
But runTask also returns a `Boolean` with the return status of the task. The logic should be *run the tasks sequentially as long as isAutoRun is true until one of the tasks fails*
oxbow_lakes
A: 

How about Iterable's takeWhile:

def takeWhile(p : (A) => Boolean): Iterable[A]

Returns the longest prefix of this iterable whose elements satisfy the predicate p

param p - the test predicate.

return - the longest prefix of this iterable whose elements satisfy the predicate p.

Yardena
Again - it's not quite the right fit for the intent I don't think. `takeWhile` (to me) implies that I'll do something with the resulting collection. I'm beginning to think the reason this is unanswerable is that these *functional-style* operations (i.e. ones with a `A => Boolean` predicate) only fit with operations which have no side effects, whereas my `runTask` does have them.
oxbow_lakes
+3  A: 

In scala 2.8, you can do things like this:

breakable{
    tasks.foreach{ task =>
        if(!(workflow.isAutoRun&&runTask(task))) break
    }
}
Eastsun
+1  A: 

Would this work?

def doWhile[T](iter: Iterator[T])(predicate: T => Boolean): Unit = 
  if(iter.hasNext && predicate(iter.next())) 
    doWhile(iter)(predicate)

It's tail recursive so it doesn't blow the stack up. As you have to run the predicate for each element of the iterator, the predicate is a function from T to Boolean.

Your code would then reduce to:

doWhile(it) {
   task => workflow.isAutoRun &&
           runTask(task)
   }

Due to the side effect nature of your actions, the action is already performed when you evaluate the predicate (not very nice, but if that's how the legacy code works, you have to work around it :).

A more functional way of doing it would be along the lines of:

def doWhile[T](iter: Iterator[T])(predicate: => Boolean)(action: T => Unit): Unit = 
  if(iter.hasNext && predicate) {
    action(iter.next)
    doWhile(iter)(predicate)(action) 
  }

Note: I might find a better name for this method than doWhile, but it's late ...

Hope it helps :)

-- Flaviu Cipcigan

Flaviu Cipcigan
+4  A: 

Similarly to Flaviu's answer above, you can put an implicit definition in scope somewhere:

  implicit def conditionalLoop[T](s: Seq[T]) = {
    new {
      def doWhile(p: T => Boolean): Unit = {
        doWhile(s.elements)(p)
      }
      private def doWhile(it: Iterator[T])(p: T => Boolean): Unit = {
        if (it.hasNext && p(it.next)) doWhile(it)(p)
      }
    }
  }

Then, calling is convenient:

tasks doWhile {
  t => workflow.isAutoRun && t.run
}
Mitch Blevins
This is pretty much exactly what I was looking for - but I wasn't sure about the tail recursion
oxbow_lakes