2015-04-24 4 views
5

Spesso mi ritrovo a scrivere Scala della forma:Idiomatic Scala per le opzioni al posto di if/else/else catena

def foo = { 
    f1() match { 
    case Some(x1) => x1 
    case _ => 
     f2() match { 
     case Some(x2) => x2 
     case _ => 
      f3() match { 
      case Some(x3) => x3 
      case _ => 
       f4() 
      } 
     } 
    } 
} 

Questo è l'equivalente morale di Java

Object foo() { 
    Object result = f1(); 
    if (result != null) { 
     return result; 
    } else { 
     result = f2(); 
     if (result != null) { 
      return result; 
     } else { 
      result = f3(); 
      if (result != null) { 
       return result; 
      } else { 
       return f4(); 
      } 
     } 
    } 
} 

e sembra brutto e inutilmente prolisso. Mi sembra che ci dovrebbe essere un modo leggibile per farlo in una riga di Scala, ma non mi è chiaro cosa sia.

Nota: ho guardato allo Idiomatic Scala for Nested Options ma è un caso un po 'diverso.

risposta

7

So di strada in ritardo alla festa, ma ritengo che la soluzione orElse qui è un po 'goffo. Per me, l'idioma funzionale generale (non solo scalaico) sarebbe sth. lungo queste linee (mi perdoni, io non Scala competente):

def f1 =() => { println("f1 here"); null } 
def f2 =() => { println("f2 here"); null } 
def f3 =() => { println("f3 here"); 2 } 
def f4 =() => { println("f4 here"); 3 } 
def f5 =() => { println("f5 here"); 43 } 

Stream(f1, f2, f3, f4, f5) 
    .map(f => f()) 
    .dropWhile(_ == null) 
    .head 

si utilizza flusso come una lista pigro, e in fondo, si dice: Iniziare invocando le funzioni e mi danno il primo che non valutano a zero . La combinazione di approccio dichiarativo e pigrizia ti dà questo pezzo generico di codice, in cui l'unica cosa che devi cambiare quando cambia il numero di funzioni, è la lista di input (stream) aggiungendo solo un altro elemento di funzione. In questo modo, le funzioni f1 ... fn diventano dati, quindi non devi modificare alcun codice esistente.

4

Si potrebbe provare:

f1() orElse f2() orElse Option(f3()) getOrElse f4() 

Supponendo che la F1 e F2 restituisce le opzioni dello stesso tipo e F3 e F4 restituire un non-opzioni di quello stesso tipo

EDIT

Non è completamente chiaro dal tuo esempio se f3() restituisce un Option o meno, quindi se lo fa allora il codice sarebbe semplificato in:

f1() orElse f2() orElse f3() getOrElse f4() 
+0

@ m-z, ho aggiunto un'alternativa basata su 'f3()' restituendo un 'Opzione'. Grazie. – cmbaxter

7

Il modo idiomatico di scrivere modello nidificato corrispondenza con le opzioni di Scala è quello di utilizzare i metodi map, flatMap, orElse e getOrElse.

Si utilizza map quando si desidera elaborare il contenuto dell'opzione ulteriormente e mantenere il comportamento opzionale:

Così, invece di questo:

val opt: Option[Int] = ??? 
opt match { 
    case Some(x) => Some(x + 1) 
    case None => None 
} 

si dovrebbe fare questo:

val opt: Option[Int] = ??? 
opt.map(_ + 1) 

Questa catena è molto più naturale:

opt.map(_ + 1).map(_ * 3).map(_ - 2) 

flatMap è ugualmente simile, ma viene utilizzato quando le operazioni successive restituiscono anche un tipo di opzione.

Nel tuo esempio particolare, orElse sembra essere la soluzione più adatta. È possibile utilizzare orElse per restituire l'opzione stessa se non è vuota oppure restituire l'argomento. Si noti che l'argomento viene valutato pigramente, quindi è veramente equivalente alla corrispondenza del modello nidificato/if-then-else.

def foo = { 
    f1().orElse(f2()) 
     .orElse(f3()) 
     .orElse(f4()) 
} 

È possibile anche combinare questi con getOrElse se si vuole sbarazzarsi di un opzione. Nel tuo esempio ti sembra di tornare la finale f4 come se non ha restituito un Option, quindi si dovrebbe effettuare le seguenti operazioni:

def foo = { 
    f1().orElse(f2()) 
     .orElse(f3()) 
     .getOrElse(f4()) 
} 
0

Un lieve ritocco sulla risposta cmbaxter che consente di risparmiare un paio di caratteri, ma è comunque lo stesso.

def foo = f1 orElse f2 orElse f3 getOrElse f4 
Problemi correlati