8

我有几个遵循这种模式的代码块:

// Dummy function defs.
def result(i : Int, d : Double, b : Boolean) = {
    if (b) d else i
}

def fA(s : String) = {7}
def fB(s : String, i : Int) = {1.0}
def fC(s : String, i : Int, d : Double) = {true}

// Actual code.
def test(s : String) : Double = {
    try {
        val a = fA(s) 
        try {
            val b = fB(s, a)
            try {
                val c = fC(s, a, b)
                result(a, b, c)
            } catch {
                case _ => result(a, b, false)
            }

        } catch {
            case _ => result(a, 0.0, false)
        }
    } catch {
        case _ => result(0, 0.0, false)
    }
}

其中 a、b 和 c 由相应的函数依次计算,然后将值传递给结果函数。如果在任何阶段发生异常,则使用默认值代替其余变量。

有没有更惯用的方式来表达这段代码。它让我想起了 Monads,因为它是一系列链式计算,如果任何计算失败,它们会立即退出。

4

8 回答 8

5

我不确定您是否可以使用 monad,因为在每个步骤中您都有两种选择(异常或结果)并且忠实于您的原始代码,在您不想调用fBorfC函数的异常情况下。

我无法优雅地删除默认值的重复,所以我将其保留,因为我认为它更清晰。这是我基于either.foldand的非单子版本control.Exception

def test(s : String) = {
  import util.control.Exception._
  val args = 
    allCatch.either(fA(s)).fold(err => (0, 0.0, false), a => 
      allCatch.either(fB(s, a)).fold(err => (a, 0.0, false), b =>
        allCatch.either(fC(s, a, b)).fold(err => (a, b, false), c =>
          (a, b, c))))
  (result _).tupled(args)
}
于 2012-06-27T07:51:31.570 回答
5

这些类型的问题正是Try旨在解决更多单子的问题(比嵌套try/catch块)。

Try表示可能导致异常或返回成功计算值的计算。它有两个子类——SuccessFailure

非常有趣的是这个问题突然出现了——几天前,我完成了一些添加和重构scala.util.Try,对于 2.10 版本,这个 SO 问题有助于说明我们最终决定的组合器的一个重要用例包括; transform.

(在撰写本文时,transform目前在夜间,将从 2.10-M5 开始在 Scala 中,今天或明天到期。有关Try和使用示例的更多信息可以在夜间文档中找到)

使用transform(通过嵌套它们),这可以使用Trys 来实现,如下所示:

def test(s: String): Double = {
  Try(fA(s)).transform(
    ea => Success(result(0, 0.0, false)), a => Try(fB(s, a)).transform(
      eb => Success(result(a, 0.0, false)), b => Try(fC(s, a, b)).transform(
        ec => Success(result(a, b, false)), c => Try(result(a, b, c))
      )
    )
  ).get
}
于 2012-07-11T09:28:50.770 回答
4

我将示例更改为使用 monad:

def fA(s: String) = Some(7)
def fB(i: Option[Int]) = Some(1.0)
def fC(d: Option[Double]) = true // might be false as well

def result(i: Int, d: Double, b: Boolean) = {
  if (b) d else i
}

def test(s: String) = result(fA(s).getOrElse(0), fB(fA(s)).getOrElse(0.0), fC(fB(fA(s))))

注意: for-comprehension 被解释为 chained flatMap。所以 的类型resOption[(Int, Double, Boolean)]。因此,无需编写mapflatMap自己编写。编译器为您完成工作。:)

编辑

我编辑了我的代码以使其适合所有可能性。如果我找到更好的方法,我会改进它。感谢您的所有评论。

于 2012-06-27T07:00:43.893 回答
3

通过定义这些效用函数

implicit def eitherOps[E, A](v: Either[E, A]) = new {
  def map[B](f: A => B) = v match {
    case Left(e)  => Left(e)
    case Right(a) => Right(f(a))    
  }

  def flatMap[B](f: A => Either[E, B]) = v match {
    case Left(e)  => Left(e)
    case Right(a) => f(a)
  }

  def or(a: A) = v match {
    case Left(_) => Right(a)
    case x       => x          
  }
}

def secure[A, B](f: A => B) = new {
  def run(a: A): Either[Trowable, B]  = try {
    Right(f(a))
  } catch {
    case e => Left(e)
  }
}

并简化你的

def fA(s : String) = 7
def fB(i : Int) = 1.0
def fC(d : Double) = true

我们会有:

def test(s: String): Either[Throwable, Double] =  for {
  a <- secure(fA).run(s).or(0)
  b <- secure(fB).run(a).or(0.0)
  c <- secure(fC).run(b).or(false)
} yield result(a, b, c)

编辑

这是一个可执行但更冗长的代码片段

object Example {
  trait EitherOps[E, A] {
    def self: Either[E, A]

    def map[B](f: A => B) = self match {
      case Left(e)  => Left(e)
      case Right(a) => Right(f(a))    
    }

    def flatMap[B](f: A => Either[E, B]) = self match {
      case Left(e)  => Left(e)
      case Right(a) => f(a)
    }

    def or(a: A) = self match {
      case Left(_) => Right(a)
      case x       => x          
    }
  }

  trait SecuredFunction[A, B] {
    def self: A => B

    def secured(a: A): Either[Throwable, B]  = try {
      Right(self(a))
    } catch {
      case e => Left(e)
    }
  }

  implicit def eitherOps[E, A](v: Either[E, A]) = new EitherOps[E, A] {
    def self = v
  }

  implicit def opsToEither[E, A](v: EitherOps[E, A]) = v.self

  implicit def secure[A, B](f: A => B) = new SecuredFunction[A, B]{
    def self = f
  }

  def fA(s : String) = 7
  def fB(i : Int) = 1.0
  def fC(d : Double) = true

  def result(i : Int, d : Double, b : Boolean) = {
    if (b) d else i
  }

  def test(s: String): Either[Throwable, Double] =  for {
    a <- (fA _).secured(s) or 0
    b <- (fB _).secured(a) or 0.0
    c <- (fC _).secured(b) or false
  } yield result(a, b, c)
}
于 2012-06-27T13:19:34.443 回答
1

您可以catching按如下方式使用成语:

import scala.util.control.Exception._

def test(s : String) : Double = result(
  catching(classOf[Exception]).opt( fA(s) ).getOrElse(0),
  catching(classOf[Exception]).opt( fB(s, a) ).getOrElse(0.0),
  catching(classOf[Exception]).opt( fC(s, a, b) ).getOrElse(false)
)

但是,与其他解决方案类似,这确实会在执行上进行轻微更改,fB并且fC将始终对其进行评估,而您的原始代码仅在先前的调用成功时才评估它们。

于 2012-06-27T07:08:38.107 回答
1

我发现大多数 try-catch 问题可以通过使用map + recoveror的组合来解决flatMap + recoverWith。更喜欢链接表达式而不是嵌套它们。

我以两种不同的方式尝试了这一点:

第一个假设您需要嵌套,并且 fA、fB、fC,结果实际上比您的示例更复杂;尽管这些更现实的例子将有助于提出一个简洁明了的解决方案并编写一些测试用例

def test(s: String): Double =
  Try (fA(s)) flatMap (a =>
    Try (fB(s, a)) flatMap (b => 
      Try (result(a, b, fC(s, a, b))) recover { case _ => result(a, b, false) }
      ) recover { case _ => result(a, 0.0, false) }
    ) getOrElse result(0, 0.0, false)

如果 fA、fB、fC 的结果确实像它们一样令人讨厌,我们可以对这个 IMO 有一个更简单的解决方案。如果它们在处理异常时没有任何副作用,你可以这样做getOrElse如果它们确实有副作用,例如记录异常,你可以使用recoverrecoverWith

def test(s: String) = {
  val evalA = Try(fA(s)).getOrElse(0)
  val evalB = Try(fB(s, evalA)).getOrElse(0.0)
  val evalC = Try(fC(s, evalA, evalB)).getOrElse(false)

  result(evalA, evalB, evalC)
}

于 2019-03-06T15:45:27.617 回答
0

以前的答案似乎错过了您希望每个级别都有默认结果的事实。这里不需要花哨的 for 表达式,你只需要一个辅助函数:

def optTry[T]( f: => T) : Option[T] = try { Some(f) } catch { case e:Exception => None }

好的,optTry这是一个坏名字(我不擅长那个游戏),但是,你可以:

def test(s : String) : Double = {
  val a = optTry(fA(s)) getOrElse 0
  val b = optTry(fB(s,a)) getOrElse 0.0
  val c = optTry(fC(s,a,b)) getOrElse false

  result(a,b,c)
}

请注意,Scala 2.10 将具有一个Try数据结构,该结构基本上使用 pimpedEither代替 执行相同的操作Option,请参阅:http ://www.scala-lang.org/archives/downloads/distrib/files/nightly/docs/library/ index.html#scala.util.Try

另请注意,这try { ... } catch { case _ => ... }是一个坏主意,您当然不想捕获一些系统异常,例如 OutOfMemory 等。

编辑:另外,请参阅 ScalazValidation数据结构,了解所有此类问题的敬畏世界。请参阅:http ://scalaz.googlecode.com/svn/continuous/latest/browse.sxr/scalaz/example/ExampleValidation.scala.html

于 2012-06-27T07:31:59.867 回答
0

试图让它更实用。不确定这个解决方案是否比你的更清晰,但我认为如果你有更多的计算步骤,它应该更合适。

def result(i : Int, d : Double, b : Boolean) = {
    if (b) d else i
}

def fA(s : String) = {7}
def fB(s : String, i : Int) = {1.0}
def fC(s : String, i : Int, d : Double) = {true}

type Data = (Int, Double, Boolean)
def test(s : String) : Double = {
  val steps = Seq[Data => Data](
    {case (_, b, c) => (fA(s), b, c)},
    {case (a, _, c) => (a, fB(s, a), c)},
    {case (a, b, _) => (a, b, fC(s, a, b))}
  )
  val defaults: Either[Data, Data] = Right((0, 0.0, false))
  val resultData = steps.foldLeft { defaults } { (eith, func) =>
    eith match {
      case left: Left[_,_] => left
      case Right(data) => try {
        Right(func(data))
      } catch {
        case _ => Left(data)
      }
    }
  } fold (identity, identity)
  (result _) tupled (resultData)
}
于 2012-06-27T06:47:00.540 回答