我该如何在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]
- 这有点痛苦。有什么方法可以解决这个问题吗?
这有点无关您的主要问题,但我认为,一个更有效的方法(即不需要任何的异常)来实现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?
这里的问题是,shift
的if
语句中不允许无else
。编译器拒绝将Unit
转换为Unit @cps[Unit]
。
谢谢,非常有趣的做法。你愿意为那些不熟悉延续的我们添加更多的解释吗? – 2011-06-09 06:40:55
如果我们允许像'earlyReturn(4,cond =!checkOK)'这样的调用,将条件移动到布尔参数,我们可能会摆脱'cpsunit'吗?或者你会有更优雅的想法?最后:如果连续的方法仍然有效,如果其中几个嵌套? – 2011-06-09 06:53:48
>>“如果连续的方法仍然有效,如果它们中的几个嵌套?经过一些编辑,是的,它的确如此。 – 2011-06-09 08:25:28
我认为自定义异常是在这里的正确本能。
我不认为这是不好的做法。它似乎被过度使用的语言,使它很容易,并没有提供好的选择。 – 2011-06-08 16:21:38
如果你用浅的方式编写你的案例区别(使用'else if'),你的“实际计算”只能嵌套一次,而不是嵌套在你的黑客中。那有什么问题?在你的例子中,你保存的只是一个关键字'else',但你有所有的开销。 – Raphael 2011-06-08 18:03:01
@Raphael在那个例子中是*,但我已经指定了我正在查看的情况,当然,我有多个条件需要检查 - 通常是3或4,所以我的实际代码将嵌套3或4次。 – 2011-06-08 20:37:03