2015-05-23 18 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) 

至于第二个问题,这是更好地使用地图功能替代循环(尤其是当有突变的循环),因为地图是一个功能,它可以由。而且,一旦熟悉并习惯使用地图,就很容易推理。