2014-09-08 13 views
5

Son zamanlarda, sık sık böyle kod yazarken sona:Scala: Daha iyi iç içe birden koşul denetimi

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

somewhat related question, iç içe match böyle kullanımı için savunan ağır görünüyor A, her ne kadar benim bakış itibaren, bu okunaklı, özlü veya kolay anlaşılır gibi görünmüyor: (1) çekinin kendisinin ve sonradan ayrıldığını, (2) kodun kontrolsüz bir şekilde yuvalama için gerçek bir gerekçe olmadan iç içe geçmesini sağlar. Bu özel durumlarda, ben hatları kod şey yapılandırmak için mutluluk olacaktır: Ancak

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

, o model (yani valueXOpt = (...).get =>isEmpty =>value = valueXOpt.get kontrol edin) gerçekten çirkin görünüyor ve kesinlikle de çok ayrıntılı olduğunu. Cehennem, hatta Java sürümü daha özlü görünecektir:

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

maçlarla iç içe gitmeden, daha iyi, daha temiz bir alternatif, alternatif kısa kaçış yolu değerini alma ve belirtmek için yani (yani log bir çizgi + return) var mı ?

cevap

1

Belki bir durum x için, demek:

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 

ya da ben basitleştirerek bile

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 

üzgünüm.

+0

Bence haklısın ama bunu okumaktan sıkılmıyorum çünkü kodun hepsi tek bir satırda ezildi, lütfen onu güzelleştirebilirsin – samthebest

+0

@samthebest Rahatsız edemedim. Oh iyi. –

6

Bu nasıl? Sonra

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

...

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

'OrElse''i daha az verimli bir şekilde yeniden keşfettiniz. https://github.com/scala/scala/blob/2.11.x/src/library/scala/Option.scala#L287 –

1

harita yöntemini kullanmak istemiyor musunuz?

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

Yine de iç içe geçmiş ancak yukarıdaki desen eşleştirmelerinden en az zarif görünümlü.

Yoksa bu bir için kendi funktor uygulayabilirsiniz:

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

Bu kod arg.get bir Foo döndüren varsayar.