Domanda

Option monade è un ottimo modo espressiva di affrontare le cose qualcosa o niente a Scala. Ma cosa succede se uno ha bisogno di registrare un messaggio quando si verifica "niente"? Secondo la documentazione delle API Scala,

  

Il tipo O è spesso usato come un   alternativa al scala.Option dove Sinistra   rappresenta guasto (per convenzione) e   A destra è simile ad alcuni.

Tuttavia, non ho avuto la fortuna di trovare le migliori pratiche utilizzando o buoni esempi reali che coinvolgono entrambi i casi per errori di elaborazione. Alla fine mi è venuta in mente il seguente codice per il mio progetto:

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

(Si prega di notare che questo è un frammento di un vero e proprio progetto, in modo che non si compila da solo)

sarei grato di sapere come si utilizza Either nel codice e / o migliori idee sul refactoring del codice di cui sopra.

È stato utile?

Soluzione

In entrambi viene utilizzato per restituire uno dei possibili due risultati significativi, a differenza di opzione che viene utilizzato per restituire un unico risultato significativo o nulla.

Un facile da capire esempio è riportata qui sotto (diffuso su Scala mailing list un po 'indietro):

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

Come suggerisce il nome della funzione, se l'esecuzione di "blocco" è successo, tornerà "Right ()". In caso contrario, se un Throwable è gettato, verrà restituito "Left ()". Utilizzare pattern matching per elaborare il risultato:

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

La speranza che aiuta.

Altri suggerimenti

biblioteca Scalaz ha qualcosa di simile entrambi i casi il nome di convalida. E 'più idiomatica di entrambi per l'uso come "ottenere sia un risultato valido o un fallimento".

La convalida consente inoltre di accumulare errori.

Modifica: "allo stesso modo" O è complettly falsa, perché convalida è un funtore applicativa, e scalaz entrambi i casi, chiamato \ / (pronunciato "disjonction" o "uno"), è una monade. Il fatto che la convalida può accumalate errori è a causa di quella natura. D'altra parte, / ha una natura "stop precoce", fermandosi al primo - \ / (leggere "sinistra", o "errore") che incontra. C'è una spiegazione perfetta qui: http://typelevel.org/blog /2014/02/21/error-handling.html

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

Come richiesto dal commento, copia / incolla del link qui sopra (alcune righe rimossi):

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

Il frammento che hai postato sembra molto artificiosa. Si utilizza in una situazione in cui:

  1. Non è sufficiente sapere solo i dati non sono disponibili.
  2. È necessario restituire uno dei due tipi distinti.

Passando un'eccezione in un sinistro è, infatti, un caso d'uso comune. Nel corso try / catch, ha il vantaggio di mantenere il codice insieme, che ha senso se l'eccezione è un risultato atteso . Il modo più comune di gestire entrambi i casi è il pattern matching:

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

Un altro modo interessante di gestire Either è quando appare in una raccolta. Quando si fa una mappa su un insieme, un'eccezione potrebbe non essere praticabile, e si consiglia di restituire alcune informazioni diverso da "non è possibile". L'utilizzo di un O consente di farlo senza sovraccaricare l'algoritmo:

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

Qui abbiamo un elenco di tutti gli autori in biblioteca, più un elenco di libri senza autore. Così possiamo poi ulteriormente elaborare di conseguenza:

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

Quindi, di base Sia l'utilizzo va così. Non è una classe particolarmente utile, ma se fosse avresti visto prima. D'altra parte, non è inutile sia.

I gatti ha un bel modo per creare un aut dal codice di eccezione-lancio:

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

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

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top