54

Optionモナドは、Scalaで何かまたは何もないことに対処するための優れた表現方法です。しかし、「何も起こらない」ときにメッセージをログに記録する必要がある場合はどうなるでしょうか。Scala APIのドキュメントによると、

どちらかのタイプは、scala.Optionの代わりによく使用されます。ここで、Leftは(慣例により)失敗を表し、RightはSomeに似ています。

ただし、Eitherを使用したベストプラクティス、または処理の失敗にEitherを含む実際の良い例を見つけることができませんでした。最後に、自分のプロジェクト用に次のコードを考え出しました。

    def logs: Array[String] = {
        def props: Option[Map[String, Any]] = configAdmin.map{ ca =>
            val config = ca.getConfiguration(PID, null)
            config.properties getOrElse immutable.Map.empty
        }
        def checkType(any: Any): Option[Array[String]] = any match {
            case a: Array[String] => Some(a)
            case _ => None
        }
        def lookup: Either[(Symbol, String), Array[String]] =
            for {val properties <- props.toRight('warning -> "ConfigurationAdmin service not bound").right
                 val logsParam <- properties.get("logs").toRight('debug -> "'logs' not defined in the configuration").right
                 val array <- checkType(logsParam).toRight('warning -> "unknown type of 'logs' confguration parameter").right}
            yield array

        lookup.fold(failure => { failure match {
            case ('warning, msg) => log(LogService.WARNING, msg)
            case ('debug, msg) =>   log(LogService.DEBUG, msg)
            case _ =>
        }; new Array[String](0) }, success => success)
    }

(これは実際のプロジェクトからのスニペットであるため、それ自体ではコンパイルされないことに注意してください)

Eitherコードでどのように使用されているか、および/または上記のコードをリファクタリングするためのより良いアイデアを知っていただければ幸いです。

4

4 に答える 4

55

どちらも、可能な 2 つの意味のある結果のうちの 1 つを返すために使用されます。これは、単一の意味のある結果を返すか、何も返さないために使用される Option とは異なります。

わかりやすい例を以下に示します (少し前に Scala メーリング リストで回覧されました)。

def throwableToLeft[T](block: => T): Either[java.lang.Throwable, T] =
  try {
    Right(block)
  } catch {
    case ex => Left(ex)
  }

関数名の通り、「block」の実行が成功すると「Right(<result>)」を返します。それ以外の場合、Throwable がスローされると、"Left(<throwable>)" が返されます。パターン マッチングを使用して結果を処理します。

var s = "hello"
throwableToLeft { s.toUpperCase } match {
  case Right(s) => println(s)
  case Left(e) => e.printStackTrace
}
// prints "HELLO"

s = null
throwableToLeft { s.toUpperCase } match {
  case Right(s) => println(s)
  case Left(e) => e.printStackTrace
}
// prints NullPointerException stack trace

それが役立つことを願っています。

于 2009-07-28T14:35:15.083 に答える
15

Scalaz ライブラリには、Validation という名前のいずれかのようなものがあります。「有効な結果または失敗のいずれかを取得する」として使用する場合は、Either よりも慣用的です。

検証により、エラーを蓄積することもできます。

編集: 「alike」Either は完全に false です。なぜなら、Validation は applicative functor であり、\/ (「disjonction」または「either」と発音) という名前の scalaz either はモナドだからです。Validation がエラーを蓄積できるのは、その性質によるものです。一方、/ は「早期停止」の性質を持ち、最初に遭遇した -\/ (「左」または「エラー」と読む) で停止します。ここに完全な説明があります: http://typelevel.org/blog/2014/02/21/error-handling.html

参照: http://scalaz.googlecode.com/svn/continuous/latest/browse.sxr/scalaz/example/ExampleValidation.scala.html

コメントで要求されたとおり、上記のリンクをコピーして貼り付けます (一部の行は削除されています)。

// Extracting success or failure values
val s: Validation[String, Int] = 1.success
val f: Validation[String, Int] = "error".fail

// It is recommended to use fold rather than pattern matching:
val result: String = s.fold(e => "got error: " + e, s => "got success: " + s.toString)

s match {
  case Success(a) => "success"
  case Failure(e) => "fail"
}

// Validation is a Monad, and can be used in for comprehensions.
val k1 = for {
  i <- s
  j <- s
} yield i + j
k1.toOption assert_≟ Some(2)

// The first failing sub-computation fails the entire computation.
val k2 = for {
  i <- f
  j <- f
} yield i + j
k2.fail.toOption assert_≟ Some("error")

// Validation is also an Applicative Functor, if the type of the error side of the validation is a Semigroup.
// A number of computations are tried. If the all success, a function can combine them into a Success. If any
// of them fails, the individual errors are accumulated.

// Use the NonEmptyList semigroup to accumulate errors using the Validation Applicative Functor.
val k4 = (fNel <**> fNel){ _ + _ }
k4.fail.toOption assert_≟ some(nel1("error", "error"))
于 2011-02-13T16:12:18.007 に答える
7

あなたが投稿したスニペットは非常に不自然なようです。次のような状況でEitherを使用します。

  1. データが利用できないことを知るだけでは十分ではありません。
  2. 2つの異なるタイプのいずれかを返す必要があります。

例外を左に変えることは、確かに一般的なユースケースです。try / catchよりも、コードをまとめておくという利点があります。これは、例外が期待される結果である場合に意味があります。どちらかを処理する最も一般的な方法は、パターンマッチングです。

result match {
  case Right(res) => ...
  case Left(res) => ...
}

もう1つの興味深い処理方法はEither、コレクションに表示される場合です。コレクションに対してマップを実行する場合、例外をスローすることは実行可能ではない可能性があり、「不可能」以外の情報を返したい場合があります。どちらかを使用すると、アルゴリズムに負担をかけずにそれを行うことができます。

val list = (
  library 
  \\ "books" 
  map (book => 
    if (book \ "author" isEmpty) 
      Left(book) 
    else 
      Right((book \ "author" toList) map (_ text))
  )
)

ここでは、ライブラリ内のすべての著者のリストと、著者のいない本のリストを取得します。したがって、それに応じてさらに処理することができます。

val authorCount = (
  (Map[String,Int]() /: (list filter (_ isRight) map (_.right.get))) 
   ((map, author) => map + (author -> (map.getOrElse(author, 0) + 1)))
  toList
)
val problemBooks = list flatMap (_.left.toSeq) // thanks to Azarov for this variation

したがって、基本的などちらの使用法もそのようになります。これは特に便利なクラスではありませんが、もしそうなら、以前に見たことがあるでしょう。一方で、それも無駄ではありません。

于 2009-07-28T17:09:51.533 に答える