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は、対応する関数によって順番に計算され、値が結果関数に渡されます。いずれかの段階で例外が発生した場合、残りの変数の代わりにデフォルト値が使用されます。

このコードを表現するためのより慣用的な方法はありますか?これは、計算が失敗した場合にすぐに解決する一連の連鎖計算であるという点で、モナドを思い出させます。

4

8 に答える 8

5

fB各ステップで2つの選択肢(例外または結果)があり、またはfC関数を呼び出したくない場合を除いて、元のコードに忠実であるため、モナドを使用できるかどうかはわかりません。

デフォルト値の重複をエレガントに削除することができなかったので、より明確だと思うのでそのままにしました。either.foldこれがとに基づいた私の非モナディックバージョンです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例外が発生するか、正常に計算された値を返す可能性のある計算を表します。これらには2つのサブクラスがありますSuccess-とFailure

この質問が表示されたときに非常に面白いです-数日前、scala.util.Try2.10リリースのためにいくつかの追加とリファクタリングを完了しました。このSO質問は、最終的に決定したコンビネータの重要なユースケースを説明するのに役立ちます含む; transform

(これを書いている時点でtransformは、現在は夜間であり、2.10-M5以降、今日または明日公開される予定です。詳細Tryと使用例については、夜間のドキュメントを参照してください) 。

(それらをネストすることにより)を使用すると、これは次のようにsをtransform使用して実装できます。Try

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

モナドを使用するように例を変更しました。

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は、連鎖として解釈されflatMapます。したがって、のタイプはresですOption[(Int, Double, Boolean)]mapしたがって、自分で書く必要はありませんflatMap。コンパイラがあなたに代わって作業を行います。:)

編集

すべての可能性に合うようにコードを編集しました。もっと良い方法を見つけたら、それを改善します。コメントありがとうございます。

于 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

map + recoverほとんどのtry-catchの問題は、またはの組み合わせを使用することで解決できることがわかりましたflatMap + recoverWith。式をネストするよりも、式を連鎖させることをお勧めします。

私は2つの異なる方法でこれに挑戦しました:

1つ目は、ネストと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場合は、次のようにすることができます。例外のログ記録などの副作用がある場合は、recoverまたはを使用できます。recoverWith

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

前の回答は、各レベルでデフォルトの結果が必要であるという事実を見逃しているようです。ここでの表現に夢中になる必要はありません。必要なのはヘルパー関数だけです。

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

OK、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は、代わりにpimpedを使用して基本的に同じことを行うデータ構造を持っていることに注意してくださいTry。http://www.scala-lang.org/archives/downloads/distrib/files/nightly/docs/library/参照してください。 index.html#scala.util.TryEitherOption

また、これ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 に答える