2013-09-21 503 views
1

我和斯卡拉之間有一些誤解斯卡拉返回不返回

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 foo(list.tail, count + 1)'它將工作。 布它不解釋(爲我)爲什麼「返回1」不起作用。

+0

給你的編輯:看到我的答案的第一句話。 – Hiura

+0

你想做什麼?或者你的預期輸出是什麼? – KyelJmD

+0

@Hiura對於我來說,你的最後一句話比第一句更有價值。這導致了我的簡單規則,如:*「最後一行贏」*就返回值而言。但是這並不完全正確,因爲「返回foo()」會贏,事實上它不是最後的。 – ses

回答

8

如果你看過我的完整說明下面再回答你的三個問題都應該是明確的,但這裏有一個簡短,明確的總結爲了大家的方便:

  1. 爲什麼打印0?這是因爲方法調用返回count,其默認值爲0 - 因此它返回0並且您打印0。如果你用count=5調用它,那麼它會打印5。 (請參閱下面使用println的示例。)
  2. 爲什麼遞歸甚至沒有「返回」(當它處於函數中間,而不是最後)時工作?您正在進行遞歸調用,因此遞歸發生,但您沒有返回遞歸調用的結果。
  3. 爲什麼不返回1?當我明確使用「返回」?它的確如此,但只有在list爲空的情況下。如果list非空,則返回count。 (再次,見下文使用println的例子)

這裏是由Odersky的從編程報價在斯卡拉(第一版可在線):

的推薦樣式方法實際上是爲了避免顯式的,尤其是多個返回語句。相反,將每個方法看作一個產生一個返回值的表達式。這個理念會鼓勵你使方法很小,把更大的方法分解成多個小方法。另一方面,設計選擇取決於設計上下文,而Scala可以很容易地編寫具有多個明確回報的方法,如果這符合您的需求。 [link]

Scala裏你很少使用return關鍵字,而是利用一切都在表達傳播的返回值回升到方法的頂級表現,而結果是再用作返回值。你可以將return看作更像breakgoto的東西,它會破壞正常的控制流程,並可能使你的代碼難以理解。

Scala沒有像Java這樣的語句,而是一切都是表達式,這意味着一切都會返回一個值。這就是爲什麼Scala擁有Unit而不是void的原因之一 - 因爲即使是Java中的void,也需要在Scala中返回一個值。下面是幾個關於表達式如何與您的代碼相關的示例:

  1. 在Java中表達式的東西在Scala中的表現相同。這意味着1+1的結果是2,並且x.y()的結果是方法調用的返回值。
  2. Java如果陳述,但斯卡拉如果表達式。這意味着斯卡拉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類型用於指示從不返回值的表達式,因此在類型推斷期間基本上可以忽略。下面是表明returnNothing結果類型簡單的例子:
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

+0

是的。它突然變得清晰和簡單。 (遺憾的是)。 – ses

1

從第一個調用(即0)返回的值爲count,而不是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

+0

是的..但爲什麼它沒有做我期待的事情?我認爲回報是回報。 – ses

+0

我添加了一個快速解釋,但你應該從一個很好的教程開始。例如。 http://docs.scala-lang.org/tutorials/scala-for-java-programmers.html – Hiura

+0

我讀過這一切。但是,謝謝。這不是缺乏閱讀,而是缺乏嘗試。 – ses

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 .....)前加上一個回報。

1

return您的工作,只是不符合你的期望,因爲你忽略了它的價值。如果你要傳遞一個空的列表,你會得到1,如你所料。 因爲你不能傳遞一個空列表,你原來的代碼是這樣的:

  • foo調用3個元素的列表和計數0(這個遞歸1)
  • 列表不爲空,所以我們不進入塊與return
  • 我們用遞歸2種元素進入foo,現在和計數1(遞歸級別2)
  • 列表不是空的,所以我們沒有得到成塊與return
  • 我們再次出現
  • 結構延續以1元進入foo,現在和計數2(遞歸級別3)
  • 列表不是空的,所以我們沒有得到成塊與return
  • 我們現在沒有元素進入foo和計數3(遞歸4級)
  • 我們進入塊與return,並返回1
  • 我們又回到了遞歸級別3.調用foo從中我們剛回來既不分配,也不返回的結果,因此它忽略。我們繼續到下一行並返回count,這與傳入的值相同,2
  • 同樣的事情發生在遞歸級別2和1上 - 我們忽略了foo的返回值,而是返回原來的count
  • count在遞歸1級爲0,這是最終的結果值
+0

很難遵循,但我想這是相當真實的。如果說簡單規則「不使用return,並使用if(){} else」? – ses

+0

if/else沒有規則 - 這只是一種風格。方法中的最後一個語句將是返回值。 –

+0

明白了。遞歸中的交易。這很簡單(可悲)。 – ses

0

你在你的程序中返回count是一個常量,並且用0初始化,所以這就是你在遞歸的頂層返回的值。