【问题标题】:How to tell the Scala compiler that a while loop will return a value?如何告诉 Scala 编译器 while 循环将返回一个值?
【发布时间】:2012-08-23 07:37:22
【问题描述】:

有些算法会在条件为真的情况下执行一个 while 循环,并且(肯定)会在某个点以 while 循环体内的 return 语句结束。例如:

def foo: Int = {
  while(true) {
    // At some time, the while loop will do a return statement inside its body
    if( ... )
      return 0
  }
}

简单示例(无语义):

def foo: Int = {
  var i = 0
  while(true) {
    i += 1
    if(i == 10)
      return 0
  }
}

Scala 编译器抱怨类型不匹配,因为 while 循环的类型为 Unit 并且编译器不知道 while 循环在某些时候会返回一个值。我们可以通过以下解决方法解决此问题:

def foo: Int = {
  var i = 0
  while(true) {
    i += 1
    if(i == 10)
      return 0
  }
  0 // !
}

但这看起来很难看。有更好的解决方法吗?或者对于这类问题有更好的解决方案?

【问题讨论】:

  • 你为什么不把测试条件放在你有true的地方?
  • 请不要使用 while(true) ... 或 ELSE,请使用参与者、Future 或并发计算中的任何结构来处理没有终止的问题。

标签: scala loops


【解决方案1】:

你可以抛出异常:

def foo: Int = {
  var i = 0
  while(true) {
    i += 1
    if(i == 10)
      return 0
  }
  throw new IllegalStateException("This should never happen")
}

编译器将停止抱怨类型不匹配,并且由于 while 循环总是返回一些东西,所以永远不会抛出异常。如果是这样,您将很快发现您做错了什么:)。

还有其他方法可以编写这个循环,它们更符合规范和 Scala 风格,但鉴于您提供的代码,这将以一种清晰简单的方式完成工作。

【讨论】:

  • 不错!值得注意的是,这是因为throw 表达式的类型为Nothing,这是所有事物的子类型——包括Int
  • 我不喜欢 Scala 的异常(或 while(true) ...),但真诚地,对于极简代码更改来说,这是一个不错的选择。
  • @jqno 谢谢,很好的解决方案。
【解决方案2】:

也许你应该只使用尾递归。它应该最终编译成非常相似的字节码:

import scala.annotation.tailrec

def foo: Int = {
  @tailrec def bar(i: Int): Int = {
    val j = i + 1
    if (j == 10) return 0
    else bar(j)
  }
  bar(0)
}

您甚至可能想要使用默认参数值支持:

@tailrec def foo(i: Int = 0): Int = {
  val j = i + 1
  if (j == 10) return 0
  else foo(j)
}

请注意,这种方式需要您将函数调用为 foo() 而不是 foo,因为它有一个参数。

【讨论】:

    【解决方案3】:

    更惯用的方法是使用递归。像这样的:

      def foo: Int = { 
        import scala.annotation.tailrec
        @tailrec def whileUnderTen(i: Int):Int = if ( i < 10) whileUnderTen(i+1) else 0
        whileUnderTen(0)
      }
    

    【讨论】:

    • 为什么这是首选的成语?对我来说,需要一个辅助函数来处理递归只是分散了样板的注意力,所以“递归更好”就像教条一样。我从来不明白反对一个循环的参数是什么,当它完成时返回(例如在collectFirst中实现)。寻求启蒙!
    • @Paul - 避免忘记增加循环会稍微容易一些(例如,如果你忘记了一个分支,编译器会抱怨你没有返回值),而且通常也更容易验证任何被更新的状态实际上都是更新的(因为你传入它)。我仍然经常更喜欢 while 循环(简单情况下的样板更少,有时更明显的终止条件),但它们都有自己的优势。
    • 对。只是对我来说,“递归更好”的响应有时有点不假思索(这里不一定是 Jan 的情况,我赶紧补充一下!)。虽然(或 for)循环不会自动成为代码异味,IMO。
    • 你有一个 def 和一个调用,而不是一个 while 和一个 var 定义。我不认为这比当务之急更样板。 ;-) 递归方法/函数的一个优点是您可以重用它们(当然不是在这个简单的示例中)
    【解决方案4】:

    对于这些场合,我在我的个人标准库中定义了一个“永远”构造。

    forever{
    }
    

    在所有方面都等同于

    while(true){
    }
    

    除了forever 的类型为Nothing 而等效的while 构造的类型为Unit。这只是让 Scala 如此有趣的小型扩展功能之一。

    【讨论】: