4

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))

3
  • Where's workflow.isAutoRun coming from? Does it change its value because of a side effect of running the tasks? Commented Oct 7, 2009 at 11:57
  • @Daniel - yes: its value may change over time although it is independent of task success/failure. Basically a user can change a workflow to put it in manual state, ensuring that this code will not attempt to complete the workflow. This is a legacy app (the isAutoRun results in a remote invocation) and cannot be modified Commented Oct 7, 2009 at 13:48
  • @Daniel - see my comments to Yardena below - I think the problem is that the legacy app I'm working with is obviously not functional in nature/style and hence doesn't fit easily with the naming of the collection manipulation methods Commented Oct 7, 2009 at 13:50

5 Answers 5

4

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

1 Comment

This is pretty much exactly what I was looking for - but I wasn't sure about the tail recursion
4

In scala 2.8, you can do things like this:

breakable{
    tasks.foreach{ task =>
        if(!(workflow.isAutoRun&&runTask(task))) break
    }
}

Comments

2

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.

1 Comment

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.
2

"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)

2 Comments

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.
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
1

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

Comments

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.