8

say, I have a bunch of "validation" functions that return None if there is no error, otherwise it return Some(String) specifying the error message. Something like the following ...

def validate1:Option[String] 
def validate2:Option[String]
def validate3:Option[String]

I am going to call them in a sequence and as soon as one returns Some(String), I stop and return the same. If it returns None, I go to the next until the sequence is over. If all of them return None, I return None.

I would like to glue them together in a "for expression". Something like ...

for( a <- validate1; b <- validate2; c <- validate3) yield None;

However, Option flows exactly the opposite what I want here. It stops at None and follows with Some(String).

How can I achieve something like that?

giampaolo
  • 6,906
  • 5
  • 45
  • 73
sanjib
  • 593
  • 1
  • 4
  • 7

4 Answers4

17

You could just chain the calls together with the orElse method on Option

validate1 orElse validate2 orElse validate3

or you could run a fold over a collection of validate methods converted to functions

val vlist= List(validate1 _, validate2 _, validate3 _)

vlist.foldLeft(None: Option[String]) {(a, b) => if (a == None) b() else a}
Don Mackenzie
  • 7,953
  • 7
  • 31
  • 32
  • 3
    +1 I'd even merge those two solutions to gain shorter and more readable form: l.foldLeft(None: Option[String]) {(a, b) => a orElse b()} – mcveat Nov 15 '10 at 14:10
  • I like the orElse chaining. Thanks Don. – sanjib Nov 15 '10 at 14:11
  • @mcveat, that's a nice refinement, @sanjib, it was a good question, orElse seems to come up quite frequently on SO. – Don Mackenzie Nov 15 '10 at 14:48
  • 1
    You could also use `Stream` + `find`: `(validate1 #:: validate2 #:: validate3 #:: Stream.empty).find(_.isDefined).flatMap(identity)`. I would've used `flatten` at the end there, but it comes out as a list. – Nick Partridge Nov 15 '10 at 22:31
3

The scalaz library has a type called Validation which allows for some incredible gymnastics with building both errors and success. For example, suppose you have a few methods which can either return a failure message or some successful outcome (A/B/C):

import scalaz._; import Scalaz._
def fooA : ValidationNEL[String, A]
def fooB : ValidationNEL[String, B]
def fooC : ValidationNEL[String, C]

These can be used with the applicative functor to chain the calls together:

(foo1 <|**|> (foo2, foo3)) match {
  case Success( (a, b, c) ) => //woot
  case Failure(msgs)        => //erk
}

Note that if any one of foo1/2/3 fails, then the whole composition fails with a non-empty list (NEL) of failure messages. If more than one fails, you get all failure messages.

It's a killer app. Examples of how tor return a success and failure are as follows

def foo1 : ValidationNEL[String, Int] = 1.success
def foo2 : ValidationNEL[String, Double] = "some error msg".failNel
oxbow_lakes
  • 133,303
  • 56
  • 317
  • 449
2

Can't you just combine the iterators together and then take the first element? Something like:

scala> def validate1: Option[String] = {println("1"); None}
scala> def validate2: Option[String] = {println("2"); Some("error")}
scala> def validate3: Option[String] = {println("3"); None}
scala> (validate1.iterator ++ validate2.iterator ++ validate3.iterator).next
1
2
res5: String = error
Steve
  • 3,038
  • 2
  • 27
  • 46
  • The last line could be simplified a little bit: `(validate1 ++ validate2 ++ validate3).head` – pr1001 Nov 15 '10 at 13:20
  • 2
    I don't think you want to drop the `.iterator` calls - then you'll always call all the validate methods instead of only calling methods until one of them returns an error. – Steve Nov 15 '10 at 13:41
0

I think you might benefit from using Lift's Box, which has Full (i.e. Some), Empty (i.e. None) and Failure (an Empty with a reason why it's empty and that can be chained). David Pollak has a good blog post introducing it. In short, you might do something like this (not tested):

def validate1: Box[String]
def validate2: Box[String]
def validate3: Box[String]
val validation = for (
  validation1 <- validate1 ?~ "error message 1"
  validation2 <- validate2 ?~ "error message 2"
  validation3 <- validate3 ?~ "error message 3"
) yield "overall success message"

This isn't any shorter than the original example but it's, in my opinion, a bit more logical, with the result of a successful validation in a Full and a failed validation in Failure.

However, we can get smaller. First, since our validation function return Box[String], they can return Failures themselves and we don't need to transform Empty to Failure ourselves:

val validation = for (
  validation1 <- validate1
  validation2 <- validate2
  validation3 <- validate3
) yield "overall success message"

But, Box also has an or method that returns the same Box if it is Full or the other Box if it is not. This would give us:

val validation = validate1 or validate2 or validate3

However, that line stops at the first validation success, not the first failure. It might make sense to make another method that does what you want (perhaps called unless?) though I can't say that it would really be much more useful than the for comprehension approach.

However, here's a little library pimping that does it:

scala> class Unless[T](a: Box[T]) {
     | def unless(b: Box[T]) = {
     | if (a.isEmpty) { a }
     | else b
     | }
     | }
defined class Unless

scala> implicit def b2U[T](b: Box[T]): Unless[T] = new Unless(b)
b2U: [T](b: net.liftweb.common.Box[T])Unless[T]

scala> val a = Full("yes")                                      
a: net.liftweb.common.Full[java.lang.String] = Full(yes)

scala> val b = Failure("no")                                    
b: net.liftweb.common.Failure = Failure(no,Empty,Empty)

scala> val c = Full("yes2")                                     
c: net.liftweb.common.Full[java.lang.String] = Full(yes2)

scala> a unless b
res1: net.liftweb.common.Box[java.lang.String] = Failure(no,Empty,Empty)

scala> a unless b unless c
res2: net.liftweb.common.Box[java.lang.String] = Failure(no,Empty,Empty)

scala> a unless c unless b
res3: net.liftweb.common.Box[java.lang.String] = Failure(no,Empty,Empty)

scala> a unless c
res4: net.liftweb.common.Box[java.lang.String] = Full(yes2)

This is a quick hack based upon my limited understanding of Scala's type system, as you can see in the following error:

scala> b unless a
<console>:13: error: type mismatch;
 found   : net.liftweb.common.Full[java.lang.String]
 required: net.liftweb.common.Box[T]
       b unless a
                ^

However, that should be enough to get you on the right track.

Of course the Lift ScalaDocs have more information on Box.

pr1001
  • 21,727
  • 17
  • 79
  • 125