1

我和 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”在上面不起作用。

4

5 回答 5

8

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

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

这是 Odersky 引用的Programming in Scala的一段话(第一版可在线获取):

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

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

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

  1. Java 中的表达式在 Scala 中的行为相同。这意味着1+1is的结果2,而 的结果x.y()是方法调用的返回值。
  2. Java 有 if语句,但 Scala 有 if表达式。这意味着 Scala if/else构造的行为更像Java 三元运算符。因此,if (x) y else z相当于x ? y : z在Java中。一个像你用的孤单if是一样的if (x) y else Unit
  3. Java 中的代码块是由一组语句组成的语句,但在 Scala 中,它是由一组表达式组成的表达式。代码块的结果是块中最后一个表达式的结果。因此,{ oa(); 的结果 ob(); oc() } 是o.c()返回的任何内容。您可以使用C/C++ 中的逗号运算符进行类似的构造:(o.a(), o.b(), o.c())。Java真的没有这样的东西。
  4. 关键字打破了表达式中的return正常控制流,导致当前方法立即返回给定值。你可以把它想象成抛出一个异常,既因为它是正常控制流的异常,又因为(像throw关键字一样)结果表达式具有 type Nothing。该Nothing类型用于指示从不返回值的表达式,因此在类型推断期间基本上可以忽略。这是一个简单的示例,显示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

于 2013-09-21T17:08:39.500 回答
1

您将返回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.

于 2013-09-21T16:28:13.757 回答
1

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。

于 2013-09-21T16:40:09.970 回答
1

你的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
  • 递归级别 1的值为count0,这是最终结果
于 2013-09-21T16:40:55.443 回答
0

你在你的程序中返回计数,它是一个常数,初始化为 0,所以这就是你在递归的顶层返回的。

于 2013-09-21T17:32:56.743 回答