Reputation: 13342
There is some misunderstanding between me and Scala
0 or 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
}
--- EDIT:
It will work if I use return
here "return foo(list.tail, count + 1)'
.
Bu it does NOT explain (for me) why "return 1" does not work above.
Upvotes: 1
Views: 2061
Reputation: 33019
If you read my full explanation below then the answers to your three questions should all be clear, but here's a short, explicit summary for everyone's convenience:
count
, which had a default value of 0
—so it returns 0
and you print 0
. If you called it with count=5
then it would print 5
instead. (See the example using println
below.)list
is empty. If list
is non-empty then it returns count
instead. (Again, see the example using println
below.)Here's a quote from Programming in Scala by Odersky (the first edition is available online):
The recommended style for methods is in fact to avoid having explicit, and especially multiple, return statements. Instead, think of each method as an expression that yields one value, which is returned. This philosophy will encourage you to make methods quite small, to factor larger methods into multiple smaller ones. On the other hand, design choices depend on the design context, and Scala makes it easy to write methods that have multiple, explicit returns if that's what you desire. [link]
In Scala you very rarely use the return
keyword, but instead take advantage that everything in an expression to propagate the return value back up to the top-level expression of the method, and that result is then used as the return value. You can think of return
as something more like break
or goto
, which disrupts the normal control flow and might make your code harder to reason about.
Scala doesn't have statements like Java, but instead everything is an expression, meaning that everything returns a value. That's one of the reasons why Scala has Unit
instead of void
—because even things that would have been void
in Java need to return a value in Scala. Here are a few examples about how expressions work that are relevant to your code:
1+1
is 2
, and the result of x.y()
is the return value of the method call.if
/else
construct acts more like the Java ternary operator. Therefore, if (x) y else z
is equivalent to x ? y : z
in Java. A lone if
like you used is the same as if (x) y else Unit
.o.c()
returned. You can make similar constructs with the comma operator in C/C++: (o.a(), o.b(), o.c())
. Java doesn't really have anything like this.return
keyword breaks the normal control flow in an expression, causing the current method to immediately return the given value. You can think of it kind of like throwing an exception, both because it's an exception to the normal control flow, and because (like the throw
keyword) the resulting expression has type Nothing
. The Nothing
type is used to indicate an expression that never returns a value, and thus can essentially be ignored during type inference. Here's simple example showing that return
has the result type of Nothing
:def f(x: Int): Int = {
val nothing: Nothing = { return x }
throw new RuntimeException("Can't reach here.")
}
Based on all that, we can look at your method and see what's going on:
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
}
Now you should be able to see that count
is being used as the result of your method, except in the case when you break the normal control flow by using return
. You can see that the return
is working because foo(List(), 5)
returns 1
. In contrast, foo(List(0), 5)
returns 5
because it's using the result of the block, count
, as the return value. You can see this clearly if you try it:
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
You should restructure your method so that the value that the body is an expression, and the return value is just the result of that expression. It looks like you're trying to write a method that returns the number of items in the list. If that's the case, this is how I'd change it:
def foo(list:List[Int], count:Int = 0): Int = {
if (list.isEmpty) count
else foo(list.tail, count + 1)
}
When written this way, in the base case (list is empty) it returns the current item count, otherwise it returns the result of the recursive call on the list's tail with count+1
.
If you really want it to always return 1
you can change it to if (list.isEmpty) 1
instead, and it will always return 1
because the base case will always return 1
.
Upvotes: 8
Reputation: 31
The fact that you do not have a return in front of foo(list.tail, count + 1) means that, after you return from the recursion, execution is falling through and returning count. Since 0 is passed as a default value for count, once you return from all of the recursed calls, your function is returning the original value of count.
You can see this happening if you add the following println to your code:
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
}
To fix this you should add a return in front of foo(list.tail.....).
Upvotes: 1
Reputation: 8481
you return count in your program which is a constant and is initialized with 0, so that is what you are returning at the top level of your recursion.
Upvotes: 0
Reputation: 10667
Your return
works, just not the way you expect because you're ignoring its value. If you were to pass an empty list, you'd get 1 as you expect.
Because you're not passing an empty list, your original code works like this:
foo
called with List of 3 elements and count 0 (call this recursion 1)return
foo
, now with 2 elements and count 1 (recursion level 2)return
foo
, now with 1 element and count 2 (recursion level 3)return
foo
with no elements and count 3 (recursion level 4)return
and return 1foo
from which we just came back in neither assigned nor returned, so it's ignored. We proceed to the next line and return count, which is the same value that was passed in, 2foo
and instead return the original count
count
on the recursion level 1 was 0, which is the end resultUpvotes: 1
Reputation: 3530
You're returning the value of count
from the first call (that is, 0
), not the value from the recursive call of foo.
To be more precise, in you code, you don't use the returned value of the recursive call to foo.
Here is how you can fix it:
def foo(list:List[Int], count:Int = 0): Int = {
if (list.isEmpty) {
1
} else {
foo(list.tail, count + 1)
}
}
This way, you get 1
.
By the way, don't use return
. It doesn't do always what you would expect.
In Scala, a function return implicitly the last value. You don't need to explicitly write return
.
Upvotes: 1