2014-09-08 10 views
5

Ultimamente, ho spesso finisco per scrivere codice così:Scala: meglio nidificato condizione multipla controllo

def doSomethingWithLotsOfConditions(arg1, arg2, arg3...) { 
    arg1.get(arg2) match { 
    case Some(value1) => 
     arg3.get(value1) match { 
     case Some(value2) => 
      arg4.get(arg5, value2) match { 
      case Some(value3) => 
       finallyDoSomethingInside(value3) 
      case None => 
       log("Some excuse for being unable to work with arg4/arg5...") 
      } 
     case None => 
      log("Some excuse for being unable to work with arg3") 
     } 
    case None => 
     log("Some excuse for being unable to work with arg1/arg2") 
    } 
} 

Un somewhat related question sembra sostengono pesantemente per tale uso di nidificato match, anche se, dal mio punto di vista, difficilmente sembra leggibile, conciso o facile da capire: (1) questo tipo di suddivide il controllo stesso e le sue conseguenze, (2) rende il codice annidabile in modo incontrollabile senza alcuna vera ragione per il nidificazione. In questi casi particolari, sarei felice di strutturare il codice qualcosa in linee di:

def doSomethingWithLotsOfConditions(arg1, arg2, arg3...) { 
    // Step 1 
    val value1Opt = arg1.get(arg2) 
    if (value1Opt.isEmpty) { 
    log("Some excuse for being unable to work with arg1/arg2") 
    return 
    } 
    val value1 = value1Opt.get 

    // Step 2 
    val value2Opt = arg3.get(value1) 
    if (value2Opt.isEmpty) { 
    log("Some excuse for being unable to work with arg3") 
    return 
    } 
    val value2 = value2Opt.get 

    // Step 3 
    val value3Opt = arg4.get(arg5, value2) 
    if (value3Opt.isEmpty) { 
    log("Some excuse for being unable to work with arg4/arg5...") 
    return 
    } 
    val value3 = value3Opt.get 

    // All checked - we're free to act! 
    finallyDoSomethingInside(value3) 
} 

Tuttavia, quel modello (vale a dire valueXOpt = (...).get => controllare isEmpty =>value = valueXOpt.get) sembra davvero brutto ed è anche decisamente troppo prolisso. L'inferno, anche la versione Java apparirebbe più concisa:

Value1Type value1 = arg1.get(arg2); 
if (value1 != null) { 
    log("Some excuse for being unable to work with arg1/arg2"); 
    return; 
} 

v'è una migliore, un'alternativa più pulita, vale a dire per ottenere il valore e specificando alternativa breve percorso di fuga (che log una linea + return), senza andare nidificato con i fiammiferi ?

risposta

1

Forse vuol dire, per una condizione x:

scala> def f(x: Option[Int]): Int = x orElse { println("nope"); return -1 } map (_ + 1) getOrElse -2 
f: (x: Option[Int])Int 

scala> f(Some(5)) 
res3: Int = 6 

scala> f(None) 
nope 
res4: Int = -1 

o anche

scala> def f(x: Option[Int], y: Option[Int]): Int = (for (i <- x orElse { println("nope"); return -1 }; j <- y orElse { println("gah!"); return -2 }) yield i + j) getOrElse -3 
f: (x: Option[Int], y: Option[Int])Int 

scala> f(Some(5), None) 
gah! 
res5: Int = -2 

Scusate se sto banalizzando.

+0

Penso che tu abbia ragione, ma non mi prendo la briga di leggerlo perché il tuo codice è tutto schiacciato su singole linee, per favore lo puoi valorizzare – samthebest

+0

@samthebest Non potevo essere disturbato neanche io. Oh bene. –

6

Che ne dici di questo?

object Options{ 
    implicit class OptionLog[T](val option:Option[T]) extends AnyVal{ 

    def ifNone(body: =>Unit):Option[T] = option.orElse { 
     body 
     option 
    } 
    } 
} 

import Options._ 

def something(arg1:Option[Int], arg2:Option[String], arg3:Option[Long], arg4:Option[Any]){ 
    for{ 
    val1 <- arg1 ifNone(println("arg1 was none")) 
    val2 <- arg2 ifNone(println("arg2 was none")) 
    val3 <- arg3 ifNone(println("arg3 was none")) 
    }{ 
    println(s"doing something with $val1, $val2, $val3") 
    } 
} 

Poi ...

scala> something(Some(3), Some("hello"), None, Some("blah")) 
arg3 was none 

scala> something(Some(3), Some("hello"), Some(10l), Some("blah")) 
doing something with 3, hello, 10 
+1

Hai reinventato 'orElse' in modo meno efficiente. https://github.com/scala/scala/blob/2.11.x/src/library/scala/Option.scala#L287 –

1

Non si desidera utilizzare il metodo di carta?

def doSomethingWithLotsOfConditions(arg1, arg2, arg3...) = 
    arg1.get(arg2).map(value1 => 
    arg3.get(value1).map(value2 => 
     arg4.get(arg5, value2).map(value3 => 
     finallyDoSomethingInside(value3)). 
     getOrElse(log("Some excuse for being unable to work with arg4/arg5"))). 
    getOrElse(log("Some excuse for being unable to work with arg3"))). 
    getOrElse(log("Some excuse for being unable to work with arg1/arg2")) 

È ancora nidificato ma ha un aspetto almeno più elegante rispetto a quello del modello sopra riportato.

Oppure è possibile implementare il proprio Functor per questo uno:

trait Foo[+A] { 
    def fmap[B](f: A => B): Foo[B] 
} 

case class Bar[A](value: A) { 
    def fmap[B](f: A => B): Foo[B] = Bar(f(value)) 
} 

case object Error[Nothing](message: String) { 
    def fmap[B](f: Nothing => B) = Error(message) 
} 

def doSomethingWithLotsOfConditions(arg1, arg2, arg3, arg4, arg5) = 
    arg1.get(arg2).fmap(value1 => 
    arg3.get(value1).fmap(value2 => 
     arg4.get(arg5, value2).fmap(value3 => 
     finallyDoSomethingInside)) 

def processResult = doSomethingWithLotsOfConditions(...) match { 
    case Bar(a) => doSomething 
    case Error(message) => log(message) 
} 

Questo codice presuppone che arg.get restituisce un Foo.

Problemi correlati