2009-07-28 15 views
46

Option monad è un ottimo modo espressivo per gestire qualcosa o qualcosa in Scala. Ma cosa succede se è necessario registrare un messaggio quando "nulla" si verifica? Secondo la documentazione delle API Scala,Uso di Either per elaborare errori nel codice Scala

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

Tuttavia, non ho avuto fortuna di trovare le migliori pratiche che utilizzano Either o esempi di esempi reali che riguardano Either per l'elaborazione dei guasti. 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)

mi piacerebbe essere grati di sapere come stai usando Either nel tuo codice e/o idee migliori sul refactoring del codice precedente.

+1

riesco a trovare alcuna menzione di qualunque cosa nel libro di Odersky, neanche. – skaffman

+4

Sì, ho "Programmazione in Scala" e non ho trovato alcuna menzione di Either in là. La migliore analogia che conosco è Box in Liftweb che viene utilizzato allo scopo di portare anche i guasti - è come Option, ma con funzionalità extra. –

+0

Qualunque alternativa migliore all'opzione [O [Foo, Bar]] '? – Jus12

risposta

44

O viene utilizzato per restituire uno dei due possibili risultati significativi, diversamente da Opzione che viene utilizzata per restituire un risultato significativo o nulla.

un facile 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à "A destra (< risultato>)". Altrimenti, se viene lanciato un Throwable, verrà restituito "Left (< throwable>)". Usa lo schema di corrispondenza 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 

Spero che questo aiuti.

+5

Peculiar ... perché non lanciare l'eccezione? – skaffman

+10

Avere un codice di gestione delle eccezioni ovunque è brutto e difficile da gestire. Usa throwableToLeft trasforma la gestione delle eccezioni in pattern matching che, imho, è più facile da leggere e mantenere. –

+24

Ad esempio, è possibile che diversi attori eseguano calcoli diversi contemporaneamente, alcuni dei quali restituiscono effettivamente un risultato e alcuni generano un'eccezione. Se butti l'eccezione, alcuni di quegli attori potrebbero non aver ancora iniziato a lavorare, perdi i risultati di qualsiasi attore che non ha ancora finito, ecc. Con questo approccio, tutti gli attori restituiranno un valore (alcuni 'Left', alcuni 'Giusto') e finisce per essere molto più facile da gestire. –

6

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

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

Trasformare un'eccezione in una sinistra è, in effetti, un caso d'uso comune. Over try/catch, ha il vantaggio di mantenere unito il codice, il che ha senso se l'eccezione è un risultato previsto. Il modo più comune di gestire entrambi i casi è il pattern matching:

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

altro modo interessante di gestire Either è quando appare in una collezione. Quando si esegue una mappa su una raccolta, lanciare un'eccezione potrebbe non essere valida e si potrebbe voler restituire alcune informazioni diverse 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, oltre a un elenco di libri senza autore.Quindi possiamo quindi elaborarlo 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, l'utilizzo di base è così. Non è una lezione particolarmente utile, ma se lo avessi visto prima. D'altra parte, non è neanche inutile.

+0

flatMap {_.left.toSeq} sembra restituire lo stesso problema, giusto? –

+0

Sì, lo farebbe. Sapevo che c'era un trucco flatMap, ma non riuscivo a trovarlo quando ho scritto l'esempio. –

12

La libreria Scalaz ha qualcosa di simile O ha chiamato Validazione. È più idiomatico di quanto sia per "ottenere un risultato valido o un fallimento".

La convalida consente inoltre di accumulare errori.

Modifica: "simile" O è completamente falso, perché Validazione è un funtore applicativo, e scalaz O, chiamato \/(pronunciato "disgiunzione" o "entrambi"), è una monade. Il fatto che Validazione possa accumulare errori è a causa di questa natura. D'altra parte,/ha una natura "stop early", fermandosi al primo - \/(leggilo "left", o "error") 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")) 
+2

Sarebbe bello vedere un esempio qui nella risposta. Applicato al tipo di problemi sollevati qui nella domanda. –

+0

'flatMap', e quindi per la comprensione su' Validation' è stato deprecato in Scalaz 7.1 e rimosso in Scalaz 7.1. Gli esempi nella risposta non funzionano più. Vedi [discussione sulla deprecazione] (https://groups.google.com/forum/#!topic/scalaz/Wnkdyhebo2w) – kostja