2015-05-23 17 views
2

我正在使用scala學習函數式編程。一般來說,我注意到for循環在功能程序中並沒有太多用處,而是使用map。用於功能性編程中的vs地圖

問題

  1. 什麼是使用地圖上的for循環中的表現,等可讀性 - 方面的優勢?

  2. 當使用循環可以實現地圖函數時,引入地圖函數的目的是什麼?

方案1:使用For循環

val num = 1 to 1000 
val another = 1000 to 2000 
for (i <- num) 
{ 
    for (j <- another) 
    { 
    println(i,j) 
    } 
} 

方案2:使用地圖

val num = 1 to 1000 
val another = 1000 to 2000 
val mapper = num.map(x => another.map(y => (x,y))).flatten 
mapper.map(x=>println(x)) 

兩個方案1和方案2做同樣的事情。

+0

對於您給(打印輸出)的例子,您不會使用map,但會使用iter。當你將一個集合元素從類型A轉換爲類型B時,映射就是你所使用的。 – BitTickler

+0

請注意,Scala中的'for'循環被編譯器轉換爲包括'map'和'flatMap'在內的操作 - 參見[this question]( http://docs.scala-lang.org/tutorials/FAQ/yield.html),例如 – DNA

+0

@DNA所以for循環只是一個合成糖,它將被編譯器轉換爲map和flatMap。 – Knight71

回答

2

其實答案很簡單。

每當您在集合上使用循環時,它都具有語義目的。要麼迭代集合的項目並打印它們。或者你想將元素的類型轉換爲另一種類型(地圖)。或者你想改變基數,比如計算一個集合的元素總和(fold)。

當然,所有這些也可以使用for循環來完成,但是對於代碼讀者來說,與知名的命名操作(比如map)相比,找出循環具有哪種語義目的還有更多的工作要做, iter,fold,filter,...

另一方面是,for循環導致使用可變狀態的黑暗面。你如何將一個集合中的元素在一個沒有可變狀態的for循環中求和?你不會。相反,你需要編寫一個遞歸函數。因此,爲了好的措施,最好早點放棄思考循環的習慣,並享受勇於嘗試的新功能。

+0

這意味着for循環被避免,因爲這使得程序員想到一個遞歸函數來解決問題。 – Knight71

+0

我花了幾個月的時間去習慣它。但是一旦你準備好了新的「一攬子技巧」,你就不會真的錯過循環,「我需要一個循環」的想法會導致你的手指輸入遞歸。但是,因爲我不是一個scala程序員,只是一個警告。我不確定Scala支持尾遞歸。不過,我也不確定相反。 – BitTickler

+1

斯卡拉確實有一些尾遞歸支持 - 請參閱[這個問題](https://stackoverflow.com/questions/3114142/what-is-the-scala-annotation-to-ensure-a-tail-recursive-function-is -優化) – DNA

1

您提供的兩個程序是而不是一樣,即使輸出可能表明它們是。這是事實,for內涵是由編譯器去加糖,但你的第一個程序實際上相當於:

val num = 1 to 1000 
val another = 1000 to 2000 
num.foreach(i => another.foreach(j => println(i,j))) 

應當注意的是,上面所得到的類型(和你的示例程序)是Unit

在第二個節目的情況下,程序的類型得到的是,如由編譯器,Seq[Unit]確定 - 這是現在一個Seq具有環構件的產品的長度。因此,您應始終使用foreach來指示導致Unit結果的效應。

2

我將從引用開始Scala編程。 「每個表達式都可以用三個高階函數map,flatMap和filter來表示。這部分描述了Scala編譯器也使用的翻譯方案。「 http://www.artima.com/pins1ed/for-expressions-revisited.html#23.4

因此,您注意到for-loops的原因並不多,因爲它們在技術上並不需要,而且對於表達式你看到的僅僅是語法糖,編譯器會翻譯成一些等價的語法。上面的鏈接中列出了將表達式翻譯成map/flatMap/filter表達式的規則:

一般來說,在函數式編程中,沒有索引變量可以進行變異,這意味着通常會大量使用函數調用(通常以遞歸的形式),比如列表摺疊而不是一段時間或循環。

對於使用列表摺疊來代替while/for循環的一個很好的例子,我推薦Tony Morris的「向你自己解釋列表摺疊」。 https://vimeo.com/64673035

如果一個函數是尾遞歸的(用@tailrec表示),那麼它可以進行優化,以避免在遞歸函數中高度使用堆棧。在這種情況下,編譯器可以將尾遞歸函數轉換爲「while循環等效」。

要回答問題1的第二部分,有些情況下可以說一個for表達式更清楚(儘管肯定也有相反的情況。)一個這樣的例子在Coursera.org課程「Scala的函數編程」由馬丁·奧德斯基博士:

for { 
    i <- 1 until n 
    j <- 1 until i 
    if isPrime(i + j) 
} yield (i, j) 

可以說是比

(1 until n).flatMap(i => 
    (1 until i).withFilter(j => isPrime(i + j)) 
    .map(j => (i, j))) 

更清晰欲瞭解更多信息,請查閱馬丁·奧德斯基博士的「函數式編程使用Scala」當然在Coursera.org上。第6.5講「For的翻譯」特別詳細討論了這一點。

而且,作爲一個快速側面說明,在您的例子中,你使用

mapper.map(x => println(x)) 

人們普遍所接受使用的foreach在這種情況下,因爲你有副作用的意圖。此外,還有手短

mapper.foreach(println) 

至於第二個問題,這是更好地使用地圖功能替代循環(尤其是當有突變的循環),因爲地圖是一個功能,它可以由。而且,一旦熟悉並習慣使用地圖,就很容易推理。