Использование любого из них для обработки сбоев в коде Scala

StackOverflow https://stackoverflow.com/questions/1193333

Вопрос

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

Любой тип часто используется в качестве альтернативы scala.Вариант, где Left представляет сбой (по соглашению), а Right сродни Some.

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

    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 в вашем коде и / или лучших идеях по рефакторингу приведенного выше кода.

Это было полезно?

Решение

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

Простой для понимания пример приведен ниже (некоторое время назад он был распространен в списке рассылки Scala).:

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

Как следует из названия функции, если выполнение "block" будет успешным, оно вернет "Right(<result>)".В противном случае, если выбрасывается выбрасываемый объект, он вернет "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

Надеюсь, это поможет.

Другие советы

Библиотека Scalaz имеет нечто подобное Либо с именем Validation.Это более идиоматично, чем использовать как "получить либо действительный результат, либо сбой".

Проверка также позволяет накапливать ошибки.

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

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

  1. Недостаточно просто знать, что данные недоступны.
  2. Вам нужно вернуть один из двух различных типов.

Превращение исключения в левое значение - это, действительно, распространенный вариант использования.Преимущество перед try / catch заключается в сохранении кода вместе, что имеет смысл, если исключение ожидаемый результат.Наиболее распространенным способом обработки любого из них является сопоставление с шаблоном:

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

Еще один интересный способ обращения Either это когда он появляется в коллекции.При выполнении сопоставления поверх коллекции выдача исключения может оказаться нежизнеспособной, и вы можете захотеть вернуть некоторую информацию, отличную от "невозможно".Использование Either позволяет вам сделать это, не перегружая алгоритм:

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

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

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

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

У Cats есть хороший способ создать Either из кода, генерирующего исключения:

val either: Either[NumberFormatException, Int] =
  Either.catchOnly[NumberFormatException]("abc".toInt)
// either: Either[NumberFormatException,Int] = Left(java.lang.NumberFormatException: For input string: "abc")

в https://typelevel.org/cats/datatypes/either.html#working-with-exception-y-code

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top