Scala入门之函数编程

虽然Twiiter提供了中文版Scala教程:Scala 课堂,但是这只是一个参考书,并不适合Scala初学者,Scala与Java/C#不同特点是面向函数编程,这反映在语言上的不同点,具有Java或C#经验的程序员如果了解这个不同特点,也就是掌握了打开Scala语言大门的钥匙。

  Scala的特点函数是第一等公民,而不是对象Object,函数可以作为方法参数传递,在Java中我们初学者经常要搞清一个问题,Java是按值还是按引用传递,现在Scala还可以按函数传统,也就是把函数看成一个对象引用。

   准备Scala环境,在http://www.scala-lang.org/download/下载scala,启动命令行,可以输入Scala的语句了。

  首先我们从Scala的函数开始:

  在命令后输入:(x:Int) => x * 2

  这种奇怪的格式让我们陌生,但是如果你熟悉javascript的函数如下:

function myfunc(param){

   alert("hello" + param);

}

  这是一个弹出窗口hello的函数,显示的是hellp+输入参数,这个param不只是可以传入值,也可以传入另外一个函数,为了能够传入另外一个函数作为参数,被传入的函数在写法上要改变一下,比如:

var myfunc2 = function (param){

   alert("hello" + param);

}

  可以看到,我们将函数名称移到了左边,右边就剩余function和()以及{}三个符号,这样我们可以传入myfunc了:

myfunc(myfunc2);

  我们已经理解了JS中函数传递,那么Scala中也是类似,上面的(x:Int) => x * 2 其实可以看成JS的(x:Int) {  x * 2  } ,我们使用大括号替代右箭头=>,两者意思差不多(少一个function付)。等同于js:

var myfunc3 = function (x) {

  return   x * 2  ;

}

  scala的x:Int类似Java的Int x,Int是x的类型,js是动态语言,所以类型定义是不需要的。Scala的写法是:

var myfunc = (x:Int) => x * 2

  我们自己简写成(x:Int) => x * 2也可以,和myfunc一样到处引用使用,没有名称而已,也就是匿名函数,可以作为另外一个函数的输入参数使用。如:

myfunc2( (x:Int) => x * 2);

   myfunc这个函数自己也可以被直接使用:

myfunc(2)

结果是4;

  那么myfunc(myfunc(2))是多少呢,注意,这里不是myfunc2,而是myfunc自己。

myfunc(2)

结果是8; 相当于调用了两次自己。

   面向函数编程经常形象的比喻成类似集成电路的输入输出一样:

输入--->函数运算 -->输出

  所以,这里(x:Int) => x * 2也有这三种结构:

输入x---->函数运算x * 2 ---->输出x*2的结果。

  输出x*2结果和x*2运算实际是捆绑在一起,是一体的,所以,一般我们就不显式象js中声明return x*2。Scala的"=>"符号的 右边可以认为代表细节,代表函数体,代表ReturnValue is “右边”.

 

第二步

   有了前面热身,我们对函数是第一等公民有个初步印象,下面再看看函数如何作为值传递的:

val myList = List(1,2,3,4,5)

for(x:Int <- myList) yield myfunc (x)

  yield是专门用于for循环,将新的结果写入到结果序列中,这里将myfunc(x)结果返回一个新的List,结果是:List[Int] = List(2, 4, 6, 8, 10)

  下面我们引入面向函数编程最常用的一个函数map:

myList.map((x: Int) => x * 2)

结果也是List[Int] = List(2, 4, 6, 8, 10); 也相当以将集合myList中每个元素经过(x: Int) => x * 2运算得到结果。

   从输入输出这个角度理解这个函数map,map的输入是(x: Int) => x * 2的输出,而(x: Int) => x * 2的输入是什么呢?是x,那么x从哪里来的?猜测可能是来自myList的每个元素。

  这里引入一个函数组合子(Functional Combinators)定义,组合子是一个没有自由free变量的函数。什么叫自由变量?没有孤立的变量,比如上面的变量是x不应该是一个孤立变量,其来自于myList的元素。

  这里的map对列表myList中的每一个元素都应用了 x * 2函数,并返回一个新的列表List(2, 4, 6)。我们称这个操作是map 组合子,同理还有filter操作。

for(x <- c; if cond) yield {...}

  等同于:

c.filter(x => cond).map(x => {...})

  或

c.withFilter(x => cond).map(x => {...})

  

  注意到这里for中多了一个if语句判断,也就是对列表集合中元素进行if判断,这相当于使用了filter函数,filter对传入函数计算结果为false的全部删除。返回一个布尔值的函数通常被称为谓词函数[或判定函数]。

   再看看for的另外一种形式, 集合嵌套:

for(x <- c1; y <- c2; z <-c3) {...}

等同于:

c1.foreach(x => c2.foreach(y => c3.foreach(z => {...})))

  使用组合子foreach的好处这里也可以看出,使用for进行嵌套循环,经常可能会迷糊钻晕了,而使用组合子则简单明了。foreach 这个组合子类似Map,但是不返回任何结果,

val doubled = myList.foreach((x: Int) => x * 2)  
doubled: Unit = ()

  这里foreach返回结果为类型Unit,类似void,是空。

  flatMap 是另外一个组合子,flat可以理解为折叠或嵌套的意思。

for(x <- c1; y <- c2; z <- c3) yield {...}

等同于

c1.flatMap(x => c2.flatMap(y => c3.map(z => {...})))

  这里的for循环和上面区别多了一个yield,看到yield第一反应我们是想到map,但是这里集合不是一个,而是三个嵌套,那么我们就使用flat+map.注意到,z是嵌套集合最后一个输出,作为map的输入。

  再看一个例子,假设有嵌套集合如下:

 val nestedNumbers = List(List(1, 2), List(3, 4))
nestedNumbers.flatMap(x => x.map(_ * 2)

返回结果是

List[Int] = List(2, 4, 6, 8)

  将两个集合折合成一个集合输出,并应用了x*2函数计算。这里_ * 2等同于(x: Int) => x * 2,下划线_表示通配上下文的任何变量或函数。是一种简单写法。

第三步

  让我们还是围绕(x: Int) => x * 2继续展开,它代表一个有输入和输出的函数,如果我们在另外一个函数中需要用这个函数作为输入参数,那么如何定义另外一个函数的方法参数呢?myfunc2(_*2)是一种hard code写法。

def myfunc2(fn: Int => Int): Int = {

  fn(10)

}

  这里的fn: Int => Int匹配(x: Int) => x * 2这样的抽象,当然也可以是(x: Int) => x + 2等,只要输入和输出返回都是整数即可。如果我们运行:

myfunc2((x: Int) => x * 2)

结果是20, 而运行:

myfunc2((x: Int) => x + 2)

结果是12。

  在这里,fn(10)中的10是fn输入参数,fn输出结果是根据myfunc2的输入决定的,有点类似访问者模式哦。

  下面我们尝试写自己的函数组合子:

var myfunc = (x: Int) => x * 2

val numbers = List(1, 2, 3, 4)

def ourMap(numbers: List[Int], fn: Int => Int): List[Int] = {
  numbers.map((x: Int) => x * 2)
}

ourMap(numbers, myfunc(_))

结果是List[Int] = List(2, 4, 6, 8)


你可能感兴趣的:(Scala入门之函数编程)