【问题标题】:Scala return does not return斯卡拉返回不返回
【发布时间】:2013-09-26 21:50:50
【问题描述】:

我和Scala之间有些误会

0 还是 1?

object Fun extends App {

 def foo(list:List[Int], count:Int = 0): Int = {

    if (list.isEmpty) { // when this is true
      return 1   // and we are about to return 1, the code goes to the next line
    }

    foo(list.tail, count + 1) // I know I do not use "return here" ...

    count
  }

  val result = foo( List(1,2,3) )

  println ( result ) // 0

}
  1. 为什么打印 0?
  2. 为什么即使没有“返回”,递归也能工作 (当它在函数中间,但不在最后)?
  3. 为什么不返回 1?当我明确使用“return”时?

--- 编辑:

如果我在这里使用return "return foo(list.tail, count + 1)',它将起作用。 但它并没有解释(对我来说)为什么“return 1”在上面不起作用。

【问题讨论】:

  • 致您的编辑:见我回答的第一句话。
  • 你想做什么?或者您的预期输出是什么?
  • @Hiura 对我来说,你的最后一句话比第一句话更有价值。这将我引向一个简单的规则,例如:“最后一行获胜” 就返回值而言。但这并不完全正确,因为“return foo()”会赢,尽管它不是最后一个。
  • @ses 只要对您有帮助,我很高兴! ;-)

标签: scala recursion return


【解决方案1】:

如果您阅读了下面我的完整解释,那么您的三个问题的答案应该都很清楚,但为了方便大家,这里有一个简短而明确的摘要:

  1. 为什么打印 0? 这是因为方法调用返回 count,它的默认值是 0——所以它返回 0,而你打印 0。如果你用count=5 调用它,那么它将打印5。 (请参阅下面使用 println 的示例。)
  2. 为什么即使没有“return”,递归也能工作(当它在函数中间,但不是在最后)?你正在进行递归调用,所以递归发生了,但你没有t 返回递归调用的结果。
  3. 为什么不返回 1?当我明确使用“return”时?确实如此,但仅限于list为空的情况。如果list 不为空,则返回count。 (同样,请参见下面使用 println 的示例。)

这里引用了 Odersky 的 Scala 编程(第一版可在线获取):

推荐的方法风格实际上是避免使用显式的,尤其是多个返回语句。相反,可以将每个方法视为产生一个值并返回的表达式。这种理念将鼓励您将方法制作得非常小,将较大的方法分解为多个较小的方法。另一方面,设计选择取决于设计上下文,如果您愿意,Scala 可以轻松编写具有多个显式返回的方法。 [link]

在 Scala 中,您很少使用 return 关键字,而是利用 表达式 中的所有内容将返回值传播回方法的顶级表达式,并且然后将结果用作返回值。您可以将return 视为更像breakgoto,它会破坏正常的控制流程,并可能使您的代码更难推理。

Scala 没有像 Java 这样的语句,而是一切都是表达式,这意味着一切都返回一个值。这就是为什么 Scala 使用 Unit 而不是 void 的原因之一——因为即使是 Java 中的 void 也需要在 Scala 中返回一个值。以下是一些与您的代码相关的表达式如何工作的示例:

  1. Java 中的表达式在 Scala 中的行为相同。也就是说1+1的结果是2x.y()的结果是方法调用的返回值。
  2. Java 有 if 语句,但 Scala 有 if 表达式。这意味着 Scala if/else 结构的行为更像 Java ternary operator。因此,if (x) y else z 等价于 Java 中的 x ? y : z。像您使用的单独的ifif (x) y else Unit 相同。
  3. Java 中的代码块是由一组语句组成的语句,但在 Scala 中,它是由一组表达式组成的表达式。代码块的结果是块中最后一个表达式的结果。因此,{ o.a(); 的结果o.b(); o.c() } 是 o.c() 返回的任何内容。您可以使用the comma operator in C/C++(o.a(), o.b(), o.c()) 进行类似的构造。 Java 没有这样的东西。
  4. return 关键字打破了表达式中的正常控制流,导致当前方法立即返回给定值。你可以把它想象成抛出一个异常,既是因为它是正常控制流的异常,也是因为(如throw 关键字)结果表达式的类型为NothingNothing 类型用于指示从不返回值的表达式,因此在类型推断期间基本上可以忽略。下面是一个简单示例,显示 return 的结果类型为 Nothing
def f(x: Int): Int = {
  val nothing: Nothing = { return x }
  throw new RuntimeException("Can't reach here.")
}

基于这一切,我们可以看看你的方法,看看发生了什么:

 def foo(list:List[Int], count:Int = 0): Int = { 
   // This block (started by the curly brace on the previous line
   // is the top-level expression of this method, therefore its result
   // will be used as the result/return value of this method.
     if (list.isEmpty) {
       return 1 // explicit return (yuck)
     }
     foo(list.tail, count + 1) // recursive call
     count // last statement in block is the result
  }

现在您应该能够看到 count 被用作您的方法的结果,除非您使用 return 破坏了正常的控制流。您可以看到return 正在工作,因为foo(List(), 5) 返回1。相反,foo(List(0), 5) 返回 5,因为它使用块的结果 count 作为返回值。试一试就能看清楚:

println(foo(List()))      // prints 1 because list is empty
println(foo(List(), 5))   // prints 1 because list is empty
println(foo(List(0)))     // prints 0 because count is 0 (default)
println(foo(List(0), 5))  // prints 5 because count is 5

您应该重新构造您的方法,使主体的值是一个表达式,而返回值只是该表达式的结果。看起来您正在尝试编写一个返回列表中项目数的方法。如果是这样的话,我会这样改变它:

 def foo(list:List[Int], count:Int = 0): Int = {
   if (list.isEmpty) count
   else foo(list.tail, count + 1)
 }

以这种方式编写时,在基本情况下(列表为空),它返回当前项目计数,否则返回列表尾部的递归调用结果,并带有count+1

如果您真的希望它始终返回1,您可以将其更改为if (list.isEmpty) 1,它将始终返回1,因为基本情况将始终返回1

【讨论】:

    【解决方案2】:

    您从第一次调用中返回 count 的值(即 0),而不是从递归调用 foo 中返回的值。

    更准确地说,在您的代码中,您不使用对 foo 的递归调用的返回值。

    以下是您可以解决的方法:

    def foo(list:List[Int], count:Int = 0): Int = {
    
        if (list.isEmpty) {
            1
        } else {
            foo(list.tail, count + 1)
        }
    }
    

    这样,您会得到1

    顺便说一句,不要使用return。它并不总是如你所愿。

    在 Scala 中,函数隐式返回最后一个值。你不需要明确写return

    【讨论】:

    • 是的.. 但为什么它不符合我的预期?我以为回报就是回报。
    • 我添加了一个快速解释,但您可能应该从一个好的教程开始。例如。 docs.scala-lang.org/tutorials/scala-for-java-programmers.html
    • 我读了这一切。不过谢谢。这不是缺乏阅读,而是缺乏尝试。
    【解决方案3】:

    您的return 工作正常,但不是您期望的方式,因为您忽略了它的价值。如果你要传递一个空列表,你会得到 1 如你所愿。 因为您没有传递一个空列表,所以您的原始代码如下所示:

    • foo 使用 3 个元素的列表和计数 0 调用(调用此递归 1)
    • 列表不为空,所以我们不会进入带有return的块
    • 我们递归输入 foo,现在有 2 个元素,计数为 1(递归级别 2)
    • 列表不为空,所以我们不会进入带有return的块
    • 我们递归输入 foo,现在有 1 个元素和 2 个计数(递归级别 3)
    • 列表不为空,所以我们不会进入带有return的块
    • 我们现在输入 foo,没有元素,计数为 3(递归级别 4)
    • 我们用return进入区块并返回1
    • 我们回到递归级别 3。调用foo 的结果是我们刚刚返回的既没有分配也没有返回,所以它被忽略了。我们继续下一行并返回计数,这与传入的值相同,2
    • 同样的事情发生在递归级别 2 和 1 - 我们忽略 foo 的返回值,而是返回原始的 count
    • count 在递归级别 1 上的值为 0,这是最终结果

    【讨论】:

    • 很难理解,但我想这是真的。如果说简单的规则“不要使用return,而使用if() {} else”?
    • if/else 没有规则——这只是一种风格。方法中的最后一条语句将是返回值。
    • 知道了。递归中的交易。这很简单(可悲)。
    【解决方案4】:

    foo(list.tail, count + 1) 前面没有 return 的事实意味着,从递归返回后,执行失败并返回 count。由于 0 作为 count 的默认值传递,一旦您从所有递归调用返回,您的函数将返回 count 的原始值。

    如果您将以下 println 添加到您的代码中,您可以看到这种情况:

    def foo(list:List[Int], count:Int = 0): Int = {
    
         if (list.isEmpty) { // when this is true
            return 1   // and we are about to return 1, the code goes to the next line
         }
    
        foo(list.tail, count + 1) // I know I do not use "return here" ...
    
         println ("returned from foo " + count)
         count
    }
    

    要解决这个问题,您应该在 foo(list.tail.....) 前面添加一个 return。

    【讨论】:

      【解决方案5】:

      您在程序中返回计数,它是一个常量并初始化为 0,因此这就是您在递归顶层返回的内容。

      【讨论】:

        猜你喜欢
        • 2020-08-07
        • 2020-08-07
        • 1970-01-01
        • 2011-11-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-11-18
        相关资源
        最近更新 更多