Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to code this validation logic in Scala?

Tags:

scala

Suppose I would like to code the following logic in Scala

val xdir = System.getProperty("XDir")
if (xdir == null)
   error("No XDir") // log the error and exit

val ydir = System.getProperty("YDir") 
if (ydir == null)
   error("No YDir")

if (!new File(xdir).isDirectory)
   error("XDir is not a directory")

if (!new File(ydir).isDirectory)
   error("YDir is not a directory")

if (!new File(xdir).exists)
   error("XDir does not exis")

if (!new File(ydir).exists)
   error("YDir does not exist")
...
(and so on)

What is the best way to code this chain of validations in Scala?

like image 568
Michael Avatar asked Jun 24 '12 14:06

Michael


3 Answers

Here's some useful things:

def sysValue(prop: String) = Option(System.getProperty(prop)) //returns Option[String]

def trySysValue(prop: String) = //returns Either[String, String]
  sysValue(prop) map Right getOrElse Left("Absent property: " + prop)

Then you can use monadic composition of Either through its right-projection

val batch = //batch is Either[String, (File, File)]
  for {
    x  <- trySysValue("XDir")).right
    xf <- dir(x).right
    y  <- trySysValue("YDir").right
    yf <- dir(y).right
  } 
  yield (xf, yf)

Where:

def dir(s: String) = { //returns Either[String, File]
  val f = new File(s)
  if (!f.exists()) Left("Does not exist: " + f)
  else if (!f.isDir()) Left("Is not a directory: " + f)
  else Right(f)
}

The left-hand-side of the Either will be an error message. This monadic composition is fail fast. You can achieve composition which will accumulate all failures (for example, if neither XDir nor YDir exist, you would see both messages) using scalaz Validation. In that case, the code would look like this:

def trySysValue(prop: String) = //returns Validation[String, String]
  sysValue(prop) map Success getOrElse ("Absent property: " + prop).fail

def dir(s: String) = {
  val f = new File(s)
  if (!f.exists())("Does not exist: " + f).fail
  else if (!f.isDir()) ("Is not a directory: " + f).fail
  else f.success
}

val batch = //batch is ValidationNEL[String, (File, File)]
  (trySysValue("XDir")) flatMap dir).liftFailNel <|*|> (trySysValue("YDir")) flatMap dir).liftFailNel
like image 99
oxbow_lakes Avatar answered Nov 16 '22 04:11

oxbow_lakes


something like:

val batch = for{
  a <- safe(doA, "A failed") either
  b <- safe(doB, "B failed") either
  c <- safe(doC, "C failed") either
} yield(a,b,c)
batch fold( error(_), doSuccess(_) )

Where safe performs a, you guessed it, safe (try/catch) operation that takes a failure (Left outcome) message and returns an Either RightProjection (which allows you to do above batch operation while threading through the point-of-failure error message)

class Catching[T](f: => T) {
  def either(msg: String) = {
    try { Right(f).right } catch { Left(msg).right }
  }
}
def safe[T](f: => T) = new Catching(f)

Can add an option method to Catching class as well, along with logging if you want to log particular error types.

See Jason Zaugg's solution for right biasing Either and this thread from scala-debate on the subject as well. No consensus as yet, but most scala "heavies" seem to be in favor.

One limitation of this approach is that if you attempt to add conditionals (if a = b) to the for{} block, it won't compile (since default Either filter method returns Option). The workaround is to implement filter and withFilter, returning Either, something I have yet to figure out/do (if someone has already done so, please post)

like image 39
virtualeyes Avatar answered Nov 16 '22 03:11

virtualeyes


Yes you can use validation without scalaz, see here for a self containt implementation : http://applicative-errors-scala.googlecode.com/svn/artifacts/0.6/chunk-xhtml/apa.html HTH

like image 1
AndreasScheinert Avatar answered Nov 16 '22 04:11

AndreasScheinert