【问题标题】:Scala while(true) type mismatch? Infinite loop in scala?Scala while(true) 类型不匹配? scala中的无限循环?
【发布时间】:2012-05-29 16:15:59
【问题描述】:

为什么要关注代码

def doSomething() = "Something"

var availableRetries: Int = 10

def process(): String = {
  while (true) {
    availableRetries -= 1
    try {
      return doSomething()
    } catch {
      case e: Exception => {
        if (availableRetries < 0) {
          throw e
        }
      }
    }
  }
}

产生以下编译错误

error: type mismatch;
 found   : Unit
 required: String
             while (true) {
             ^

?

这在 C# 中可以正常工作。 while 永远循环,所以它不能终止,因此它不能产生字符串以外的结果。或者如何在 Scala 中进行无限循环?

【问题讨论】:

标签: scala loops infinite-loop


【解决方案1】:

与基于语句的语言 C#(以及 Java 和 C 和 C++)不同,Scala 是基于表达式的语言。就可组合性和可读性而言,这主要是一个很大的优势,但在这种情况下,差异已经咬住了你。

Scala 方法隐式返回方法中最后一个表达式的值

scala> def id(x : String) = x
id: (x: String)String

scala> id("hello")           
res0: String = hello

在 Scala 中,几乎所有东西都是一个表达式。看起来像语句的东西仍然是返回一个名为 Unit 的类型的值的表达式。该值可以写成()。

scala> def foo() = while(false){}
foo: ()Unit

scala> if (foo() == ()) "yes!" else "no"
res2: java.lang.String = yes!

没有一个图灵等效语言的编译器可以检测到所有非终止循环(参见图灵停止问题),因此大多数编译器只做很少的工作来检测任何非终止循环。在这种情况下,无论 someCondition 是什么,“while(someCondition){...}”的类型都是 Unit,即使它是常量 true。

scala> def forever() = while(true){}
forever: ()Unit

Scala 确定声明的返回类型 (String) 与实际返回类型 (Unit) 不兼容,后者是最后一个表达式的类型 (while...)

scala> def wtf() : String = while(true){}
<console>:5: error: type mismatch;
 found   : Unit
 required: String
       def wtf() : String = while(true){}

答案:在末尾添加异常

scala> def wtfOk() : String = {
     | while(true){}
     | error("seriously, wtf? how did I get here?")
     | }
wtfOk: ()String

【讨论】:

  • +1 感谢您的深入解释。但我认为 Scala 编译器应该解析常量表达式(就像 C# 编译器一样)。
  • 在这种情况下,C# 对常量表达式并不聪明。它只是将“while”视为非返回语句。您可以通过编写类似的 C# 构造来证明这一点,其中“while”中的条件不是恒定的 - 比如读取用户提供的变量。 C# 对“while”的理解与 Scala 截然不同。 C# 通过说唯一显式的“return”返回一个字符串来分析代码。 Scala 将“while”视为最后一个,因此返回的表达式。这就是基于表达式的语言(如 Scala)和基于语句的语言(如 C#)之间的区别。
  • 理论上,该语言可以有一个特殊情况来识别 while(true){...} 具有类型 Nothing,而不是类型 Unit。那么这个例子就可以正常工作,因为 Nothing 是 String 的子类型(以及其他所有内容)。几年前我在邮寄时提出了这个建议,但普遍的反应是它发生的频率不足以值得改变语言。如果你经常做这种事情,很容易创建一个你描述的“永远”方法,但它的返回类型是 Nothing 而不是 Unit。
  • @James 我明白你的意思,但我不认为你的第一句话是真的。在 C# 中尝试以下操作:int T() { while (true) return 5; } 这将编译并返回 5。但是:int T(bool f) { while (f) return 5; } 这会产生错误:not all code paths return a value
【解决方案2】:

定义无限循环的函数式方法是递归:

@annotation.tailrec def process(availableRetries: Int): String = {
  try {
    return doSomething()
  } catch {
    case e: Exception => {
      if (availableRetries < 0) {
        throw e
      }
    }
  }
  return process(availableRetries - 1)
}

elbowichretry函数没有内部loop函数:

import scala.annotation.tailrec 
import scala.util.control.Exception._ 

@tailrec def retry[A](times: Int)(body: => A): Either[Throwable, A] = { 
  allCatch.either(body) match { 
    case Left(_) if times > 1 => retry(times - 1)(body) 
    case x => x 
  } 
} 

【讨论】:

    【解决方案3】:

    不幸的是,编译器不够聪明,无法知道您不能退出 while 循环。不过,这很容易上当,即使您无法明智地生成返回类型的成员——只需抛出异常即可。

    def process(): String = {
      while (true) {
        ...
      }
      throw new Exception("How did I end up here?")
    }
    

    现在编译器会意识到即使它逃脱了while循环,它也不能在那里返回值,所以它不用担心while循环有返回类型Unit(即不返回值) .

    【讨论】:

      【解决方案4】:
      import scala.annotation.tailrec
      import scala.util.control.Exception._
      
      def retry[A](times: Int)(body: => A) = {
        @tailrec def loop(i: Int): Either[Throwable, A] =
          allCatch.either(body) match {
            case Left(_) if i > 1 => loop(i - 1)
            case x => x
          }
        loop(times)
      }
      
      retry(10) {
        shamelessExceptionThrower()
      }
      

      【讨论】:

      • 很好的答案,但您不需要内部loop 函数。
      • @senia 对。我怀疑body 是否会评估递归调用。
      【解决方案5】:

      编辑:我刚刚注意到实际的 return 语句。 while 循环中的 return 语句将被忽略。例如,在 REPL 中:

      scala> def go = while(true){return "hi"}
      <console>:7: error: method go has return statement; needs result type
         def go = while(true){return "hi"}
                              ^  
      

      你告诉编译器process() 方法返回一个String,但你的方法体只是一个while 循环,它不会返回任何东西(它是一个Unit,或一个Java void )。在 while 循环之后更改返回类型或添加字符串。

      def process(): Unit = {
         while(true){...}
      }
      

      def process(): String = {
        while(true){...}
        "done"
      }
      

      【讨论】:

        【解决方案6】:

        基于seniaelbowichdave我使用的解决方案如下:

        @annotation.tailrec
        def retry[T](availableRetries: Int)(action: => T): T = {
          try {
            return action
          } catch {
            case e: Exception if (availableRetries > 0) => { }
          }
          retry(availableRetries - 1)(action)
        }
        

        这可以用作肘部和戴夫的解决方案:

        retry(3) {
          // some code
        }
        

        【讨论】:

        • 请注意,如果出于任何原因您无法使函数尾递归(例如,您可能多次调用自己),则此方法不起作用。
        猜你喜欢
        • 2012-08-27
        • 2017-09-13
        • 2019-04-15
        • 1970-01-01
        • 1970-01-01
        • 2011-08-25
        • 1970-01-01
        • 1970-01-01
        • 2017-02-25
        相关资源
        最近更新 更多