Подтвердить что ты не робот

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

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

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

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

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

4b9b3361

Ответ 1

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

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

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

Как следует из названия функции, если выполнение "блока" будет успешным, оно вернет "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

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

Ответ 2

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

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

Edit: "alike". Либо полностью ложно, потому что Validation является прикладным функтором, а 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"))

Ответ 3

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

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

Превращение исключения в левое является, действительно, обычным прецедентом. По сравнению с 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))
  )
)

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

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

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