我如何实现Scala中方法体外的早期返回?

Jea*_*let 7 continuations scala type-inference control-flow

免责声明:在有人说出来之前:是的,我知道这是不好的风格,不鼓励.我只是这样做与Scala一起玩,并尝试更多地了解类型推理系统如何工作以及如何调整控制流.我不打算在实践中使用此代码.


所以:假设我处于一个相当冗长的函数中,在开始时有很多连续的检查,如果它们失败,它们都应该导致函数返回一些其他值(而不是throw),否则返回正常值.我不能return在身体中使用Function.但我可以模拟它吗?有点像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)
  }
}
Run Code Online (Sandbox Code Playgroud)

我在这里的检查显然是假的,但重点是我想避免这样的事情,实际上有趣的代码最终会因为我的口味而过于嵌套:

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 
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

我的解决方案在这里运行正常,如果我愿意的话,我可以提早返回4作为返回值.麻烦的是,我必须明确地写出类型参数[Int]- 这有点痛苦.有什么方法可以解决这个问题吗?

Vas*_*iuk 3

这与您的主要问题有点无关,但我认为,更有效的方法(不需要抛出异常)来实现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)  
}
Run Code Online (Sandbox Code Playgroud)

这里唯一的问题是您必须显式使用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())
Run Code Online (Sandbox Code Playgroud)

k上面的代码片段代表了其余的计算。根据 的值cond,我们要么返回该值,要么继续计算。

EDIT2: Any chance we might get rid of cpsunit?这里的问题是,如果没有shift.insideif语句,则不允许使用该语句else。编译器拒绝转换UnitUnit @cps[Unit].