如何反转Option Monad的流量?

时间:2010-11-15 12:07:33

标签: scala scala-option

说,我有一堆“验证”函数,如果没有错误则返回None,否则返回Some(String)指定错误消息。像下面这样......

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

我将按顺序调用它们,一旦返回Some(String),我就会停止并返回相同的内容。如果它返回None,我会转到下一个,直到序列结束。如果所有这些都返回None,则返回None。

我想把它们粘在一起“for expression”。有点像...

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

然而,Option正好与我想要的完全相反。它在None处停止,并以Some(String)跟随。

我怎样才能实现这样的目标?

4 个答案:

答案 0 :(得分:17)

您可以在选项

上使用或Else 方法将调用链接在一起
validate1 orElse validate2 orElse validate3

或者您可以对转换为函数的验证方法集合进行折叠

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

vlist.foldLeft(None: Option[String]) {(a, b) => if (a == None) b() else a}

答案 1 :(得分:3)

scalaz库有一个名为Validation的类型,它允许一些令人难以置信的体操,同时构建错误和成功。例如,假设您有一些方法可以返回失败消息或某些成功结果(A / B / C):

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

这些可以与applicative functor一起使用来将调用链接在一起:

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

请注意,如果foo1/2/3中的任何一个失败,则整个合成将失败并显示失败消息的非空列表(NEL)。如果多个失败,则会收到所有失败消息。

这是一款杀手锏。 tor如何返回成功和失败的示例如下

def foo1 : ValidationNEL[String, Int] = 1.success
def foo2 : ValidationNEL[String, Double] = "some error msg".failNel

答案 2 :(得分:2)

你不能只将迭代器组合在一起然后取第一个元素吗?类似的东西:

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

答案 3 :(得分:0)

我认为您可能会受益于使用Lift的Box,其中Full(即Some),Empty(即None)和{{1} (Failure有一个原因,它是空的,可以链接)。 David Pollak有good blog post介绍它。简而言之,您可能会做这样的事情(未经测试):

Empty

这并不比原始示例短,但在我看来,它更合乎逻辑,在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" 中验证成功,Full验证失败。

然而,我们可以变小。首先,由于我们的验证函数返回Failure,它们可以自己返回Box[String],我们不需要自己将Failure转换为Empty

Failure

但是,val validation = for ( validation1 <- validate1 validation2 <- validate2 validation3 <- validate3 ) yield "overall success message" 也有一个Box方法,如果它是or则返回相同的Box,如果不是Full则返回Box。这会给我们:

val validation = validate1或validate2或validate3

但是,该行在第一次验证成功时停止,而不是第一次失败。制作另一种能够做你想要的方法(也许叫做unless?)可能是有意义的,虽然我不能说它比理解方法真的有用得多。

然而,这里有一个小图书馆拉皮条:

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)

根据我对Scala类型系统的有限理解,这是一个快速入侵,正如您在以下错误中看到的那样:

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
                ^

然而,这应该足以让你走上正轨。

当然Lift ScalaDocs有关于Box的更多信息。