teo
teo

Reputation: 1403

Reading a date from keyboard until it's correct - in scala

I would like to continuously prompt the user to enter a date in a given format, until he/she gets it right.

This is what I've done:

def readDate(prompt: String): Date = {
    var date: Option[Date] = None
    Iterator.continually {
      val startDateString = readLine(prompt)
      val startDate = catching(classOf[ParseException]).opt(asDate(startDateString))
      date = startDate
      startDate
    }.takeWhile(_ == None).foreach {
      date =>
        println("Incorrect format. Try again.")
    }
    date.get
  }

where asDate just uses SimpleDateFormat.parse on the entered String.

Now, this seems to work, but I'm pretty sure it's not the right way.

I don't really understand how to handle these chained iterators (since both Iterator.continually and takeWhile return an instance of AbstractIterator).

I have basically two questions:

1) Is there a way to "return" startDate from Iterator.continually? I've tried and failed map-ping it. I want this in order to get rid of the var date and date = startDate.

2) If I didn't want anything to happen between the reads, what would I do with the last foreach? I've seen that nothing works if I just remove it (I think because of next() not being invoked), but is it OK to leave it there like this:

takeWhile(_ == None).foreach { date => {}} ?

Is there a better way than the "empty" foreach?

Thanks!

Upvotes: 2

Views: 151

Answers (2)

Luigi Plinge
Luigi Plinge

Reputation: 51109

I would write it as

def readDate(prompt: String): Date = 
  try asDate(readLine(prompt))
  catch { case e: ParseException => 
    println("Incorrect format. Try again.")
    readDate(prompt)
  }

Upvotes: 2

Rex Kerr
Rex Kerr

Reputation: 167891

You should use find instead of takeWhile--this will keep dropping entries until a good one comes through. Then you have an Option, so you just need

def readDate(prompt: String): Date = {
  Iterator.continually {
    catching(classOf[ParseException]).opt(asDate( readLine(prompt) ))
  }.find(_.isDefined).get
}

if you don't want to print anything. If you do want to print something, you can put it into the loop.

def readDate(prompt: String): Date = {
  Iterator.continually {
    catching(classOf[ParseException]).opt(asDate( readLine(prompt) )) match {
      case None =>
        println("Incorrect format.  Try again.")
        None
      case x => x
    }
  }.find(_.isDefined).get
}

I'm not sure that this is dramatically clearer, but it's definitely shorter and does those things you wanted.

I'd probably use a tail-recursive function instead:

import annotation.tailrec
@tailrec def readDate(prompt: String, again: Boolean = false): Date = {
  if (again) println("Incorrect format.  Try again.")
  catching(classOf[ParseException]).opt(asDate( readLine(prompt) )) match {
    case Some(date) => date
    case None => readDate(prompt, true)
  }
}

The logic seems a bit clearer to me here.

Upvotes: 3

Related Questions