Sebastian N. Sebastian N. - 2 months ago 27
Scala Question

Already existing functional way for a retry-until in Scala?

Is there a functional/Scala way to call a function repeatedly until it succeeds, while reacting to failed attempts?

Let me illustrate with an example. Suppose I want to read an integer from standard-in, and retry if the user did not in fact enter an integer.

Given this function:

def read_int(): Either[String, Int] = {
val str = scala.io.StdIn.readLine()
try {
Right(str.trim().toInt)
} catch {
case _: java.lang.NumberFormatException => Left(str)
}
}


And this anonymous functions:

val ask_for_int = () => {
println("Please enter an Int:")
read_int()
}

val handle_not_int = (s: String) => {
println("That was not an Int! You typed: " + s)
}


I would use them like this:

val num = retry_until_right(ask_for_int)(handle_not_int)
println(s"Thanks! You entered: $num")


My questions are:


  • Does something like
    retry_until_right
    already exist in Scala?

  • Could it be solved with existing facilities? (Streams, Iterators, Monads, etc.)

  • Do any of the FP libraries (scalaz?) offer something like this?

  • Could I do anything better / more idiomatic? (*)



Thanks!

*) Apart from the snake_case. I really like it.

Answer

This was my first tail-recursive implementation:

@scala.annotation.tailrec
def retry_until_right[WRONG, RIGHT](generator: () => Either[WRONG, RIGHT])(on_wrong: WRONG => Any): RIGHT = {
  generator() match {
    case Right(right) =>
      right
    case Left(wrong) =>
      on_wrong(wrong)
      retry_until_right(generator)(on_wrong)
  }
}

But wanting to re-use existing libraries, I then switched to this approach using iterators:

def retry_until_right[WRONG, RIGHT](generator: => Either[WRONG, RIGHT])(on_wrong: WRONG => Any): RIGHT =
  Iterator.continually(generator).flatMap {
    case Left(value) =>
      on_wrong(value)
      None
    case Right(value) =>
      Some(value)
  }.toSeq.head

Which can be used like this:

val num = retry_until_right(ask_for_int()) { str =>
  println("Ivalid input: " + str)
}
println("Thanks! You entered: " + num)

However, it could be argued that hiding the Iterator from plain view inside the implementation can be inflexible. What if the developer wants to perform further operations on it? (mapping, etc.)

Instead, the operations that react on "wrong" values and finally select the first "right" one can be abstracted into an "extension" class specific for iterators of the Either[L,R] type, like this:

implicit class EitherIteratorExtensions[L, R](it: Iterator[Either[L, R]]) {
  def onLeft(callback: L => Any) =
    it.map {
      case left @ Left(value) =>
        callback(value)
        left
      case right => right
    }

  // take only Right elements
  def takeRight: Iterator[R] = 
    it.flatMap {
      case Left(_) =>
        None
      case Right(value) => Some(value)
    }

  // iterate and fetch the first Right element
  def firstRight: R = {
    takeRight.toSeq.head
  }
}

Now we can comfortably use the needed methods, in succinct code, while retaining control of the Iterator like this:

val num = Iterator.continually(ask_for_int()).onLeft(handle_not_int).firstRight

println("Thanks! You entered: " + num)

Though I'm happy with this approach, I still wonder if this is not part of an existing library already...