2009-07-28 2 views
46

Option Монада - отличный выразительный способ справиться с чем-то или чем-либо в Скале. Но что, если нужно регистрировать сообщение, когда «ничего» не происходит? Согласно документации API Scala,Использование Либо для обработки сбоев в коде Scala

Тип Либо часто используется в качестве альтернативы scala.Option где левый представляет собой отказ (по соглашению) и Право сродни Некоторые.

Однако мне не повезло найти лучшие практики с использованием Либо или хороших примеров реального мира, связанных с Либо для обработки сбоев. Наконец я придумал следующий код для моего собственного проекта:

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

Я не могу найти никаких упоминаний о это вообще в книге Одерского. – skaffman

+4

Да, у меня есть «Программирование в Scala», и я не мог найти упоминания о Либо там. Лучшей аналогией, которую я знаю, является Box в Liftweb, который также используется для переноса сбоев - это как опция, но с дополнительной функциональностью. –

+0

Любая лучшая альтернатива 'Option [Либо [Foo, Bar]]'? – Jus12

ответ

44

Либо используется для возврата одного из возможных двух значимых результатов, в отличие от Option, который используется для возврата одного значимого результата или ничего.

Легко понять пример приведен ниже (распространяется на Scala в список рассылки, некоторое время назад):

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

+10

Имея код обработки исключений повсюду, уродливо и трудно управлять. Использование throwableToLeft превращает обработку исключений в соответствие шаблонов, которые, imho, легче читать и поддерживать. –

+24

Например, у вас может быть несколько актеров, выполняющих разные вычисления одновременно, некоторые из которых фактически возвращают результат, а некоторые генерируют исключение. Если вы просто выбросите исключение, некоторые из этих участников, возможно, еще не начали работать, вы теряете результаты от любых участников, которые еще не закончили, и т. Д. При таком подходе все участники вернут значение (некоторые «левые», некоторые «Правильно»), и это становится намного проще в обращении. –

6

Отсканированный фрагмент кажется очень изобретенным. Вы используете либо в ситуации, когда:

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

Здесь мы получаем список всех авторов в библиотеке, плюс список книг без автора.Таким образом, мы можем затем обработать его соответствующим образом:

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 

Итак, базовое Либо использование идет так. Это не особо полезный класс, но если бы вы это видели раньше. С другой стороны, это тоже бесполезно.

+0

list flatMap {_.left.toSeq}, кажется, возвращает те же проблемы, что и книги, верно? –

+0

Да, было бы. Я знал, что в нем есть трюк flatMap, но я не мог найти его, когда написал этот пример. –

12

В библиотеке Scalaz есть что-то похоже. Именованная валидация. Это более идиоматично, чем Либо для использования как «получить либо действительный результат, либо неудачу».

Валидация также позволяет накапливать ошибки.

Редактировать: "alike" Либо полностью ложно, потому что валидация является прикладным функтором, а scalaz. Или, названный \/(произносится как "disjonction" или "or"), является монадой. Тот факт, что валидация может накапливать ошибки, вызвана этим характером. С другой стороны,/имеет «стоп-ранний» характер, останавливаясь при первом - \/(читайте «слева» или «ошибка»), с которым он сталкивается. Существует совершенное объяснение: 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")) 
+2

Было бы неплохо увидеть пример здесь в ответе. Применяется к типу проблем, поднятых здесь в вопросе. –

+0

'flatMap' и, следовательно, для понимания в' Validation' устарел в Scalaz 7.1 и удален в Scalaz 7.1. Примеры в ответе больше не работают. См. [Обсуждение устаревания] (https://groups.google.com/forum/#!topic/scalaz/Wnkdyhebo2w) – kostja

 Смежные вопросы

  • Нет связанных вопросов^_^