Scala while(true)类型不匹配?scala中的无限循环?

Scala while(true)类型不匹配?scala中的无限循环?,scala,loops,infinite-loop,Scala,Loops,Infinite Loop,为什么要使用下面的代码 def doSomething() = "Something" var availableRetries: Int = 10 def process(): String = { while (true) { availableRetries -= 1 try { return doSomething() } catch { case e: Exception => { if (available

为什么要使用下面的代码

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


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

编辑:我刚刚注意到实际返回语句。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"}
                        ^  


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

def process(): String = {
  while (true) {
    ...
  }
  throw new Exception("How did I end up here?")
}
现在编译器将认识到,即使它逃逸了while循环,它也不能在那里返回值,因此它不担心while循环有返回类型
单元(即不返回值)。

与基于语句的语言C#(以及Java、C和C++)不同,Scala是一种基于表达式的语言。在可组合性和可读性方面,这是一个很大的优势,但在这种情况下,这种差异让你感到痛苦

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

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

scala> id("hello")           
res0: String = hello
在Scala中,几乎所有内容都是一个表达式。看起来像语句的东西仍然是返回Unit类型值的表达式。该值可以写为()

图灵等价语言的编译器无法检测所有非终止循环(c.f.图灵停止问题),因此大多数编译器很少检测任何非终止循环。在这种情况下,“while(someCondition){…}”的类型是单位,不管someCondition是什么,即使它是常量true

scala> def forever() = while(true){}
forever: ()Unit
Scala确定声明的返回类型(字符串)与实际返回类型(单位)不兼容,实际返回类型是最后一个表达式的类型(while…)


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

@annotation.tailrec def process(availableRetries: Int): String = {
  try {
    return doSomething()
  } catch {
    case e: Exception => {
      if (availableRetries < 0) {
        throw e
      }
    }
  }
  return process(availableRetries - 1)
}
基于和的解决方案,我使用了以下方法:

@annotation.tailrec
def retry[T](availableRetries: Int)(action: => T): T = {
  try {
    return action
  } catch {
    case e: Exception if (availableRetries > 0) => { }
  }
  retry(availableRetries - 1)(action)
}
然后可用作elbowich和dave的解决方案:

retry(3) {
  // some code
}

FWIW-有。+1感谢您提供有关tailrec陷阱的提示和更多信息。+1感谢您的深入解释。但我认为Scala编译器应该解析常量表达式(就像C#编译器那样)。在这种情况下,C#对常量表达式并不聪明。它只是将“while”视为不返回语句。您可以通过编写一个类似的C#构造来证明这一点,其中“while”中的条件不是常数,比如读取用户提供的变量。C#只是认为“while”与Scala非常不同。C#分析代码时说,唯一显式的“return”返回一个字符串。Scala认为“while”是最后一个表达式,因此返回。这就是基于表达式的语言(如Scala)和基于语句的语言(如C#)之间的区别。理论上,该语言可以有一个特殊情况来识别,而(true){…}没有类型,而不是类型单位。那么这个例子就可以很好地工作了,因为没有任何东西是String(以及其他所有东西)的子类型。我在几年前的邮件中提出了这一点,但普遍的反应是,这种情况发生的频率不够高,不值得改变语言。如果你经常做这类事情,创建一个你所描述的“永远”方法是很容易的,但是要让它有返回类型而不是单位。@James我理解你的意思,但我不认为你的第一句话是真的。尝试在C#中执行以下操作:
int T(){while(true)return 5;}
这将编译并返回5。但是:
int T(bool f){while(f)return 5;}
这会产生一个错误:
不是所有的代码路径都返回一个值
。答案很好,但您不需要内部
循环
函数。@senia Right。我怀疑
body
是否会在递归调用时求值。请注意,如果出于任何原因无法使函数尾部递归(例如,您可能会多次调用自己),则此操作不起作用。
scala> def forever() = while(true){}
forever: ()Unit
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
@annotation.tailrec def process(availableRetries: Int): String = {
  try {
    return doSomething()
  } catch {
    case e: Exception => {
      if (availableRetries < 0) {
        throw e
      }
    }
  }
  return process(availableRetries - 1)
}
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 
  } 
} 
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()
}
@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
}