【问题标题】:How can I reverse of flow of Option Monad?如何反转 Option Monad 的流程?
【发布时间】:2010-11-15 12:07:33
【问题描述】:

说,我有一堆“验证”函数,如果没有错误则返回 None,否则返回 Some(String) 指定错误消息。类似于以下内容...

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

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

我想将它们粘合在一起形成“表达”。有点像……

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

但是,Option 与我在这里想要的完全相反。它在 None 处停止,然后是 Some(String)。

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

【问题讨论】:

    标签: scala scala-option


    【解决方案1】:

    您可以将调用与 Option 上的 orElse 方法链接在一起

    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 我什至会合并这两个解决方案以获得更短、更易读的形式: l.foldLeft(None: Option[String]) {(a, b) => a orElse b() }
    • 我喜欢 orElse 链接。谢谢唐。
    • @mcveat,这是一个很好的改进,@sanjib,这是一个很好的问题,orElse 似乎经常出现在 SO 上。
    • 你也可以使用Stream + find: (validate1 #:: validate2 #:: validate3 #:: Stream.empty).find(_.isDefined).flatMap(identity)。我会在最后使用flatten,但它作为一个列表出现。
    【解决方案2】:

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

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

    这些可以与应用函子一起使用,将调用链接在一起:

    (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
    

    【讨论】:

      【解决方案3】:

      你不能把迭代器组合在一起然后取第一个元素吗?比如:

      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
      

      【讨论】:

      • 最后一行可以简化一点:(validate1 ++ validate2 ++ validate3).head
      • 我认为您不想放弃 .iterator 调用 - 然后您将始终调用所有验证方法,而不是仅调用方法,直到其中一个返回错误。
      【解决方案4】:

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

      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],它们可以自己返回Failures,我们不需要自己将Empty转换为Failure

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

      但是,Box 也有一个 or 方法,如果它是 Full,则返回相同的 Box,否则返回另一个 Box。这会给我们:

      val 验证 = validate1 或 validate2 或 validate3

      但是,该行在第一次验证成功处停止,而不是第一次失败。虽然我不能说它真的比 for 理解方法有用得多,但创建另一种方法来做你想做的事情(可能称为 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 的信息。

      【讨论】:

        猜你喜欢
        • 2017-12-06
        • 1970-01-01
        • 1970-01-01
        • 2017-11-10
        • 2015-05-21
        • 1970-01-01
        • 2021-07-30
        • 1970-01-01
        • 2016-08-10
        相关资源
        最近更新 更多