While not condition print error and finally return

Hi,

I am implementing a simple program: A user is asked for his age until the answer is right.

def defaultval: String = ""

def validator(answer: String): Boolean = (answer matches """\d+""") && answer.toInt > 0
  
def modifier(answer: String): Int = answer.toInt
   
def ask(question: String): String = if (defaultval equals "") "10" else defaultval
  
def validate(question: String, error: String="Error. Invalid answer.") = 
  while (!validator(ask)) print("error")

println(validate("What is your age? "))

Note that I tried my code on scalafiddle, therefore it has no object Main and readLine in ask. My question basically is: Is there a beautiful one-liner for validate ? Something like "While the answer is not valid, print an error and finally return the correct (modified) answer …

Any ideas? I am pretty sure that Scala has a beautiful solution to that. It always has beautiful solutions :wink:

For simple command-line programs, I often use something like this:

  def ask(prompt: String): Int =
    scala.io.StdIn.readLine(prompt).toIntOption match {
      case Some(n) if n > 0 => n
      case _ => ask(prompt)
    }

This will keep asking until it gets a positive integer.

Thanks for your reply, charpov. Very nice solution. However, I’d prefer a more general approach; i.e. define ask independently from how input is validated or modified. This is my current solution. Note that ask is still not working the way I want it to. Maybe someone could assist me here?

import scala.io.StdIn.readLine

object Main extends App {
     
  def defaultval: String = ""

  def prompt(question: String): String = if (defaultval.isEmpty) defaultval else readLine(s"$question: ")

  val validate: PartialFunction[Option[Int], Int] = { 
    case Some(n) if n > 0 => n 
  }

  def modify(answer: String): Int = answer.toInt

  def ask(prompt: String): Int = validate(readLine.toIntOption) andThen(modify) orElse ask(prompt)

  ask("What is your age? ")
}

Thanks, Simon

Hmm. If that’s the case, then I’d suggest that ask() should take function parameters for those.

That said, your declarations look odd to me – in particular, validate doesn’t really make sense, and andThen is an odd way to put it together. It looks to me like you’re starting from implementations, and trying to get function signatures from there. That tends to be the wrong way around: you should usually come up with the desired function signature first, then figure out how to implement it.

My recommendation is that the signature of ask() should be something like:

def ask(prompt: String, validate: Option[String] => Boolean, modify: String => Int): Int

Figure out how to fill all of those pieces in, and I suspect you’ll have roughly what you are looking for.

Extra-credit problem you might want to play with: to make this really general, to work with different questions and result types, change the signature of ask() to:

def ask[T](prompt: String, validate: Option[String] => Boolean, modify: String => T): T

I am actually trying to rewrite some python code (which is already very simplified) for the purpose of demonstrating the beauty of scala.

And yes, I know that my validate looks kind of strange but when I saw the solution of @charpov I tried to adapt it to a more general approach. When I read about PartialFunctions, I thought that these would make my ask function to be easy to read.

Note that my original goal was to write ask in a beautiful oneliner (since in scala loops simply “return” something i thought of using a loop somehow but I really liked the recursive approach given by @charpov) …

This pretty much sounds like starting from the implementation. :slight_smile:

I’d second @jducoeur and start with an even more generic signature, with validate and modify merged into transform:

def ask[T](prompt: String, transform: String => Either[String, T]): T

This could theoretically be implemented as a one-liner (beauty lies in the eye of the beholder), e.g. using Either#fold(), but to retain tail recursion, I’d rather throw in a nicely formatted match, just like in @charpov’s specialized Option version.

Then one could add more flavors implemented in terms of this base function:

def askTry[T](prompt: String, transform: String => Try[T]): T
def askOpt[T](prompt: String, transform: String => Option[T], failMsg: String = "no valid input"): T
def askPart[T](prompt: String, transform: PartialFunction[String, T], failMsg: String = "no valid input"): T
// ...

Using partial functions as the core abstraction, you’ll very likely run into similar stack growth issues as with non-tail recursion - PartialFunction is just a vanilla trait, and any combinator used in the failure path of ask, built-in ones like andThen and custom ones alike, will have to accumulate another implementing instance and another bunch of method invocations to the mix. Probably not a real issue here (if a user manages to blow the stack with wrong inputs they shouldn’t be allowed near the software in the first place), but still…

That’s a really nice signature for ask. The only suggestion I would make would be to put transform in its own parameter list. Then you could pass it in as a block:

val age = ask("What is your age?") { input =>
  input.toIntOption match {
    case Some(age) if age > 0 => Right(age)
    case _ => Left("Error. Invalid answer.")
  }
}
1 Like