Scala返回不返回

时间:2013-09-21 16:21:11

标签: scala recursion return

我与斯卡拉之间存在一些误解

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”时?
  4. ---编辑:

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

5 个答案:

答案 0 :(得分:8)

如果您在下面阅读我的完整说明,那么您的三个问题的答案应该都清楚,但这里有一个简短明了的摘要,方便每个人:

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

  4. 以下是Odersky的 Scala编程的引用(第一版可在线获取):

      

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

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

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

    1. Java中表达式的东西在Scala中表现相同。这意味着1+1的结果为2x.y()的结果是方法调用的返回值。
    2. Java有语句,但Scala有表达式。这意味着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
    5. 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
      

      您应该重构方法,以便body是表达式的值,返回值只是该表达式的结果。看起来您正在尝试编写一个返回列表中项目数的方法。如果是这种情况,这就是我改变它的方式:

       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

答案 1 :(得分: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

答案 2 :(得分:1)

你在foo(list.tail,count + 1)前面没有返回的事实意味着,在你从递归返回后,执行正在下降并返回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 .....)前添加一个返回。

答案 3 :(得分:1)

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

  • foo使用3个元素的List调用并计数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上count的值为0,这是最终结果

答案 4 :(得分:0)

你在程序中返回一个常量并且用0初始化的计数,这就是你在递归的最高层返回的内容。