简体   繁体   English

更加惯用(monadic?)方式来表达这个Scala

[英]More idiomatic (monadic?) way to express this Scala

I have several blocks of code that follow this pattern: 我有几个遵循这种模式的代码块:

// 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)
    }
}

Where a, b, & c are calculated in turn by the corresponding functions and then the values are passed to the result function. 其中a,b,&c依次由相应的函数计算,然后将值传递给结果函数。 If at any stage an exception occurs then a default value is used in place of the remaining variables. 如果在任何阶段发生异常,则使用默认值代替剩余变量。

Is there a more idiomatic way to express this code. 是否有更惯用的方式来表达此代码。 It reminds me of Monads in that it's a series of chained computations which bail out immediately if any computation fails. 它让我想起Monads,因为它是一系列链式计算,如果任何计算失败,它会立即纾困。

I'm not sure you can use monads as at each step you have two alternatives (exception or result) and to be faithful to your original code, on exception you don't want to be calling the fB or fC functions. 我不确定你是否可以使用monad,因为在每一步你有两个选择(例外或结果)并忠实于原始代码,除非你不想调用fBfC函数。

I was not able to elegantly remove the duplication of default values so I left it as I think it's clearer. 我无法优雅地删除默认值的重复,所以我离开它,因为我认为它更清楚。 Here is my non-monadic version based on either.fold and control.Exception : 这是基于either.foldcontrol.Exceptioneither.fold版本:

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)
}

These types of problems are just what Try aims to solve a bit more monadically (than nested try/catch blocks). 这些类型的问题正是Try旨在解决更多monadically(比嵌套的try/catch块)。

Try represents a computation that may either result in an exception, or return a successfully computed value. Try表示可能导致异常或返回成功计算值的计算。 It has two subclasses for these-- Success and Failure . 它有两个子类 - SuccessFailure

Very funny that this question popped up when it did-- a few days ago, I finished up some additions and refactoring to scala.util.Try , for the 2.10 release and this SO question helps to illustrate an important use-case for a combinator that we eventually decided to include; 非常有趣的是,这个问题突然出现了 - 几天前,我完成了一些补充和重构scala.util.Try ,对于2.10版本,这个SO问题有助于说明组合器的一个重要用例我们最终决定包括; transform . transform

(As of writing this, transform is currently in the nightly and will be in Scala from 2.10-M5 onward, due out today or tomorrow. More info about Try and usage examples can be found in the nightly docs ) (截至撰写本文时, transform目前在夜间 ,将在2.10-M5之后的Scala中,将于今天或明天发布。有关Try和用法示例的更多信息可在夜间文档中找到)

With transform (by nesting them), this can be implemented using Try s as follows: 通过transform (通过嵌套它们),可以使用Try s实现,如下所示:

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
}

I changed the example to use monads: 我将示例更改为使用monads:

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))))

Note: The for-comprehension is interpreted as chained flatMap . 注意:for-comprehension被解释为链式flatMap So the type of res is Option[(Int, Double, Boolean)] . 所以res的类型是Option[(Int, Double, Boolean)] Therefore there is no need to write map or flatMap by yourself. 因此,您无需自己编写mapflatMap The compiler does the work for you. 编译器为您完成工作。 :) :)

Edit 编辑

I edited my code to make it fit to all possibilitys. 我编辑了我的代码,使其适合所有可能性。 I will improve it, if I find a better way. 如果我找到更好的方法,我会改进它。 Thank you for all your comments. 感谢您的所有意见。

By defining those utility functions 通过定义那些实用功能

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)
  }
}

and simplifying yours 并简化你的

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

We'll have: 我们会有:

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)

Edit 编辑

Here's an executable but sadly, more verbose code snippet 这是一个可执行文件,但遗憾的是,更详细的代码片段

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)
}

You can use the catching idiom as follows: 您可以使用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)
)

However, similarly to other solutions, this does make a slight executional change in that fB and fC will always be evaluated, whereas your original code only evaluates them if the prior calls succeeded. 但是,与其他解决方案类似,这确实会产生轻微的执行更改,因为fBfC将始终进行评估,而原始代码仅在先前调用成功时才对其进行评估。

Tried to make it more functional. 试图让它更具功能性。 Not sure if this solution is clearer than yours, but I think it shoud fit better if you'll have more computation steps. 不确定这个解决方案是否比你的解决方案更清晰,但我认为如果你有更多的计算步骤,它会更合适。

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)
}

The previous answers seem to miss the fact that you want default result at each level. 之前的答案似乎错过了您希望每个级别的默认结果的事实。 No need to be fancy with for expression here, you just need an helper function: 这里不需要花哨的表达,你只需要一个辅助函数:

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

OK, optTry is a bad name (I'm not good at that game), but then, you can just: 好的, 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)
}

Notice that Scala 2.10 will have a Try data structure that basically does the same thing with a pimped Either in place of Option , see: http://www.scala-lang.org/archives/downloads/distrib/files/nightly/docs/library/index.html#scala.util.Try 请注意,Scala 2.10将具有Try数据结构,基本上使用pimped Either代替Option ,请参阅: http//www.scala-lang.org/archives/downloads/distrib/files/nightly/docs /library/index.html#scala.util.Try

Also notice that try { ... } catch { case _ => ... } is a bad idea, you certainly don't want to catch some system Exception like OutOfMemory and the like. 还要注意try { ... } catch { case _ => ... }是一个坏主意,你肯定不想捕获像OutOfMemory之类的系统异常。

EDIT: also, see Scalaz Validation data structure for a world of awe for all that kind of problems. 编辑:另外,请参阅Scalaz Validation数据结构,了解所有类型的问题。 See: http://scalaz.googlecode.com/svn/continuous/latest/browse.sxr/scalaz/example/ExampleValidation.scala.html 请参阅: http//scalaz.googlecode.com/svn/continuous/latest/browse.sxr/scalaz/example/ExampleValidation.scala.html

I have found most try-catch problems can be solved by using a combination of map + recover or flatMap + recoverWith . 我发现大多数try-catch问题可以通过使用map + recoverflatMap + recoverWith的组合来解决。 Prefer chaining expressions to nesting them. 首选链接表达式来嵌套它们。

I had a go at this in two different ways: 我有两种不同的方式:

First one is presuming you need the nesting and fA, fB, fC, result in reality are more complex than your example; 第一个假定你需要嵌套和fA,fB,fC,现实中的结果比你的例子更复杂; although a more realistic example of these would have been helpful in coming up with a concise and clear solution to this and writing some test cases 虽然这些更实际的例子有助于为此提出一个简洁明了的解决方案并编写一些测试用例

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)

If fA, fB, fC, result are indeed as noddy as they are, we could have a much simpler solution to this IMO. 如果fA,fB,fC,结果确实像它们一样点头,我们可以为这个IMO提供一个更简单的解决方案。 If they do not have any side-effects in handling the exception, you could do getOrElse If they do have side-effects such as logging the exception, you could use recover or recoverWith 如果他们在处理异常时没有任何副作用,你可以做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)
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM