A while back, when I was first picking up Scala, I needed to prompt the user for a line of text and keep prompting until I got a non-blank line.
Apparently, I threw the following together:
def readCompleteLine(prompt: String) = { val reader = new jline.ConsoleReader() reader.setDefaultPrompt(prompt) var line: String = null do { line = reader.readLine() } while( line != null && line.length == 0 ) line }
I came across this function yesterday and involuntarily made a gross-out sound. var's, mutability, a while loop (a do-while loop, no less!) and even the dread null
. There had to be a better way.
Using higher-order functions and recursion, I rewrote this to be less imperative, more readable and more general:
def readUntil[X](prompt: String, transform: String => X, pred: X => Boolean): X = { val reader = new jline.ConsoleReader() { setDefaultPrompt(prompt)} @tailrec def ruHelper: X = Option(reader.readLine()).map(transform(_)) match { case Some(x) if pred(x) => x case _ => ruHelper } ruHelper } /* simple, string-only version */ def readUntil(prompt: String, pred: String => Boolean): String = { readUntil(prompt, x => x, pred) }
Using this as a primitive, it's easy to write a method that repeatedly prompts the user for a string until it's non-empty.
def readUntilNonBlankLine(prompt: String) = readUntil(prompt, _.length > 0)
But it's also easy to create a method that only allows input from a list of accepted strings:
def readOneOf(prompt: String, acceptableValues: List[String]): String = readUntil(prompt, acceptableValues.contains(_))
Or even a function that reads until the value is a properly-formed floating-point number:
import util.control.Exception._ def tryToReadDouble(s: String) = catching(classOf[NumberFormatException]) opt (s.toDouble) def readUntilValidDouble(prompt: String) = ReadingUtils.readUntil(prompt, tryToReadDouble(_), (s: Option[Double]) => s.isDefined).get
Higher-order functions and genericity can really pay off - the base readUntil
is now a nicely abstracted primitive that I can test in isolation and then plug in elsewhere in my program, without having to write and rewrite error-prone imperative code.
No comments:
Post a Comment