我该如何在Scala中实现一种方法体外的早期回归?

问题描述:

声明:之前有人说:是的,我知道这是不好的风格,并没有鼓励。我只是为了与Scala一起玩,并试着更多地了解类型推断系统的工作原理以及如何调整控制流。我不打算在实践中使用此代码。我该如何在Scala中实现一种方法体外的早期回归?


所以:假设我在一个相当长的功能,有很多后续检查的开头,而如果他们失败了,都应该引起函数返回其他值(不丢) ,否则返回正常值。我不能在Function的主体中使用return。但我可以模拟它吗?有点像break模拟scala.util.control.Breaks

我想出了这一点:

object TestMain { 

    case class EarlyReturnThrowable[T](val thrower: EarlyReturn[T], val value: T) extends ControlThrowable 
    class EarlyReturn[T] { 
    def earlyReturn(value: T): Nothing = throw new EarlyReturnThrowable[T](this, value) 
    } 

    def withEarlyReturn[U](work: EarlyReturn[U] => U): U = { 
    val myThrower = new EarlyReturn[U] 
    try work(myThrower) 
    catch { 
     case EarlyReturnThrowable(`myThrower`, value) => value.asInstanceOf[U] 
    } 
    } 

    def main(args: Array[String]) { 
    val g = withEarlyReturn[Int] { block => 
     if (!someCondition) 
     block.earlyReturn(4) 

     val foo = precomputeSomething 
     if (!someOtherCondition(foo)) 
     block.earlyReturn(5) 

     val bar = normalize(foo) 
     if (!checkBar(bar)) 
     block.earlyReturn(6) 

     val baz = bazify(bar) 
     if (!baz.isOK) 
     block.earlyReturn(7) 

     // now the actual, interesting part of the computation happens here 
     // and I would like to keep it non-nested as it is here 
     foo + bar + baz + 42 // just a dummy here, but in practice this is longer 
    } 
    println(g) 
    } 
} 

我检查这里显然是假的,但主要的一点是,我想避免这样的事情,在实际有趣的代码最终被方式太嵌套对我的口味:

if (!someCondition) 4 else { 
    val foo = precomputeSomething 
    if (!someOtherCondition(foo)) 5 else { 
    val bar = normalize(foo) 
    if (!checkBar(bar)) 6 else { 
     val baz = bazify(bar) 
     if (!baz.isOK) 7 else { 
     // actual computation 
     foo + bar + baz + 42 
     } 
    } 
    } 
} 

我的解决办法正常工作在这里,我可以为返回值,年初与4返回,如果我想要的。麻烦的是,我明确写入类型参数[Int] - 这有点痛苦。有什么方法可以解决这个问题吗?

+1

我不认为这是不好的做法。它似乎被过度使用的语言,使它很容易,并没有提供好的选择。 – 2011-06-08 16:21:38

+2

如果你用浅的方式编写你的案例区别(使用'else if'),你的“实际计算”只能嵌套一次,而不是嵌套在你的黑客中。那有什么问题?在你的例子中,你保存的只是一个关键字'else',但你有所有的开销。 – Raphael 2011-06-08 18:03:01

+0

@Raphael在那个例子中是*,但我已经指定了我正在查看的情况,当然,我有多个条件需要检查 - 通常是3或4,所以我的实际代码将嵌套3或4次。 – 2011-06-08 20:37:03

这有点无关您的主要问题,但我认为,一个更有效的方法(即不需要任何的异常)来实现return将涉及延续:

def earlyReturn[T](ret: T): Any @cpsParam[Any, Any] = shift((k: Any => Any) => ret) 
def withEarlyReturn[T](f: => T @cpsParam[T, T]): T = reset(f) 
def cpsunit: Unit @cps[Any] =() 

def compute(bool: Boolean) = { 
    val g = withEarlyReturn { 
     val a = 1 
     if(bool) earlyReturn(4) else cpsunit  
     val b = 1 
     earlyReturn2(4, bool)    
     val c = 1 
     if(bool) earlyReturn(4) else cpsunit    
     a + b + c + 42 
    } 
    println(g) 
} 

唯一的问题就在这里,是你必须明确使用cpsunit

EDIT1:是,earlyReturn(4, cond = !checkOK)可以实现,但它不会是一般的优雅:

def earlyReturn2[T](ret: T, cond: => Boolean): Any @cpsParam[Any, Any] = 
          shift((k: Any => Any) => if(cond) ret else k()) 

在片段k以上表示计算的其余部分。根据cond的值,我们要么返回值,要么继续计算。

EDIT2:Any chance we might get rid of cpsunit?这里的问题是,shiftif语句中不允许无else。编译器拒绝将Unit转换为Unit @cps[Unit]

+0

谢谢,非常有趣的做法。你愿意为那些不熟悉延续的我们添加更多的解释吗? – 2011-06-09 06:40:55

+0

如果我们允许像'earlyReturn(4,cond =!checkOK)'这样的调用,将条件移动到布尔参数,我们可能会摆脱'cpsunit'吗?或者你会有更优雅的想法?最后:如果连续的方法仍然有效,如果其中几个嵌套? – 2011-06-09 06:53:48

+0

>>“如果连续的方法仍然有效,如果它们中的几个嵌套?经过一些编辑,是的,它的确如此。 – 2011-06-09 08:25:28

我认为自定义异常是在这里的正确本能。