2009-07-28 26 views
46

Option monad是處理Scala中的某件事或某件事情的絕妙表達方式。但是如果在「什麼都沒有」的情況下需要記錄消息呢?根據Scala的API文檔,使用或者處理Scala代碼中的故障

的任一類型通常用作 替代scala.Option其中左 表示失敗(約定)和 右類似於一些。

但是,我沒有運氣找到最佳實踐使用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和/或對重構上述代碼有更好的想法。

+1

我能找到沒有提及它也在Odersky的書中。 – skaffman 2009-07-28 12:35:34

+4

是的,我有「Scala程序設計」,在那裏找不到任何提及。我所知道的最好的比喻是Liftweb中的Box,用於承載失敗的目的 - 它就像選項,但具有額外的功能。 – 2009-07-28 13:16:45

+0

'Option [任何[Foo,Bar]]``更好的選擇? – Jus12 2014-05-05 12:59:06

回答

44

或者用於返回可能的兩個有意義的結果中的一個,與用於返回單個有意義結果的選項不同。

一個容易理解的例子下面給出(在斯卡拉郵件列表前陣子流傳):

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

由於功能顧名思義,如果「塊」的執行是成功的,它會返回「正確(<結果>)」。否則,如果拋出一個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 

希望有所幫助。

+5

奇特...爲什麼不只是拋出異常? – skaffman 2009-07-28 14:42:02

6

您發佈的代碼片段似乎非常有人氣。您在以下情況中使用Either:

  1. 僅僅知道數據不可用,這是不夠的。
  2. 您需要返回兩種不同類型之一。

將一個異常轉換成左邊的確是一個常見的用例。在try/catch上,它具有將代碼保存在一起的優點,如果例外是預期結果,這是有意義的。無論是處理的最常用的方法是模式匹配:

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

處理Either另一個有趣的方法是,當它出現一個收藏。在集合上執行映射時,拋出異常可能不可行,並且您可能想要返回除「不可能」之外的一些信息。使用要麼讓你這樣做不負擔過重的算法:

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

下面就讓我們看在圖書館,的書籍沒有author列表中的所有作者的列表。因此,我們可以進一步處理它:

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 

因此,基本任何一種用法都是這樣。這不是一個特別有用的課程,但如果是這樣的話,你以前就會看到它。另一方面,它也不是無用的。

12

斯卡拉茲圖書館有類似的東西或稱爲驗證。比任何一種都更習慣於「得到有效的結果或失敗」。

驗證也允許累積錯誤。

編輯:「相似」或者是完全錯誤的,因爲驗證是一個應用函數,而且標量名稱爲\ /(發音爲「disjonction」或「either」)是monad。 驗證可以糾正錯誤的事實是因爲這種性質。另一方面,/有一個「早期停止」性質,在第一個停止 -//(讀它「左」或「錯誤」)它遇到。這裏有一個完美的解釋: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"))