9

为什么下面的代码

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 中进行无限循环?

4

6 回答 6

17

与基于语句的语言 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!

图灵等效语言的编译器无法检测到所有非终止循环(参见图灵停止问题),因此大多数编译器很少做任何工作来检测任何循环。在这种情况下,“while(someCondition){...}”的类型是 Unit,无论 someCondition 是什么,即使它是常量 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
于 2012-05-29T17:26:54.807 回答
9

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

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

retry没有内部函数的肘部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 
  } 
} 
于 2012-05-29T17:39:54.677 回答
5

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

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

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

于 2012-05-29T16:52:34.497 回答
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()
}
于 2012-05-29T20:07:52.733 回答
1

编辑:我刚刚注意到实际的 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()方法返回 a String,但您的方法主体只是一个while循环,它不返回任何内容(它是 aUnit或 Java void)。在 while 循环之后更改返回类型或添加字符串。

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

或者

def process(): String = {
  while(true){...}
  "done"
}
于 2012-05-29T16:26:37.133 回答
1

基于我使用的seniabowichdave的解决方案:

@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-05-30T08:32:23.957 回答