如何在 Scala 中的方法体外部实现提前返回?

发布于 2024-11-14 06:07:11 字数 1984 浏览 3 评论 0原文

免责声明:在有人说之前:是的,我知道这是不好的风格并且不被鼓励。我这样做只是为了玩转 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]——这有点痛苦。有什么办法可以解决这个问题吗?

Disclaimer: Before someone says it: yes, I know it's bad style and not encouraged. I'm just doing this to play with Scala and try to learn more about how the type inference system works and how to tweak control flow. I don't intend to use this code in practice.


So: suppose I'm in a rather lengthy function, with lots of successive checks at the beginning, which, if they fail, are all supposed to cause the function to return some other value (not throw), and otherwise return the normal value. I cannot use return in the body of a Function. But can I simulate it? A bit like break is simulated in scala.util.control.Breaks?

I have come up with this:

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

My checks here are obviously dummy, but the main point is that I'd like to avoid something like this, where the actually interesting code ends up being way too nested for my taste:

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

My solution works fine here, and I can return early with 4 as return value if I want. Trouble is, I have to explicitly write the type parameter [Int] — which is a bit of a pain. Is there any way I can get around this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

一页 2024-11-21 06:07:11

这与您的主要问题有点无关,但我认为,实现 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: 我们有可能摆脱 cpsunit 吗? 这里的问题是 if 语句中的 shift 是没有 else 则不允许。编译器拒绝将 Unit 转换为 Unit @cps[Unit]

It's a bit unrelated to your main question, but I think, a more effective approach (that doesn't require throwing an exception) to implement return would involve continuations:

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

The only problem here, is that you have to explicitly use cpsunit.

EDIT1: Yes, earlyReturn(4, cond = !checkOK) can be implemented, but it won't be that general and elegant:

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

k in the snippet above represents the rest of the computation. Depending on the value of cond, we either return the value, or continue the computation.

EDIT2: Any chance we might get rid of cpsunit? The problem here is that shift inside the if statement is not allowed without else. The compiler refuses to convert Unit to Unit @cps[Unit].

请帮我爱他 2024-11-21 06:07:11

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

I think a custom exception is the right instinct here.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文