首页 > 编程语言 > Scala函数式编程专题--scala集合和函数
2020
10-08

Scala函数式编程专题--scala集合和函数

前情提要:

Scala函数式编程专题—— 函数式思想介绍

scala函数式编程专题——scala基础语法介绍

前面已经稍微介绍了scala的常用语法以及面向对象的一些简要知识,这次是补充上一章的,主要会介绍集合和函数。

注意噢,函数和方法是不一样的,方法是在类里面定义的,函数是可以单独存在的(严格来说,在scala内部,每个函数都是一个类)

一.scala集合介绍

还记得上一章介绍的object的apply方法吗,很多数据结构其实都用到了它,从而让我们可以直接用List(...)这样来新建一个List,而不用自己手动new一个。

PS:注意,scala默认的数据结构都是不可变的,就是说一个List,没法删除或增加新的元素。当然,也有“可变”的数据结构,后面会介绍到。

1.1 List

得益于apply方法,我们可以不通过new来新建数据结构。

//通过工厂,新建一个List,这个List是不可变的
scala> val numbers = List(1, 2, 3, 4, 5, 1, 2, 3, 4, 5)
numbers: List[Int] = List(1, 2, 3, 4, 5, 1, 2, 3, 4, 5)

1.2 元组Tuple

Tuple这个概念在python应用比较广泛,它可以将多种数据类型(Int,String,Double等)打包在一起

scala> val tup = (1,1,2.1,"tuple",'c') //将多种不同数据结构打包一起,可以有重复
tup: (Int, Int, Double, String, Char) = (1,1,2.1,tuple,c)

但在scala中,Tuple是有长度限制的,那就是一个Tuple最多只能有22个元素。

scala> val tup = (0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)
tup: (Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int, Int) = (0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)

//一个Tuple超过22个元素,报错了
scala> val tup = (0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)
<console>:1: error: too many elements for tuple: 23, allowed: 22
val tup = (0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)

可以看到上面,一但一个Tuple超过22个元素,就会报错了。至于为什么是22这个神奇的数字,好像一直没有一个统一的论调。有人开玩笑的说23才对,因为有部电影的名字叫《The Number 23》~~

1.3 Map和Option

在说Map之前,需要先介绍Option,因为Map里面存的就是Option,这个后面介绍。

Option翻译过来是选项,本质上,它也确实是一个选项,用来告诉你存不存在。还是用实例说明吧:

//Option里面可以存普通数据类型
scala> val optionInt = Option(1)
optionInt: Option[Int] = Some(1)

scala> optionInt.get
res8: Int = 1
//但一个Option也可能为空
scala> val optionNone = Option(null)
optionNone: Option[Null] = None
//当Option里面是空的时候,是get不出东西的,还会报错
scala> optionNone.get
java.util.NoSuchElementException: None.get
 at scala.None$.get(Option.scala:347)
 at scala.None$.get(Option.scala:345)
 ... 32 elided
//这个时候可以判断它就是空的
scala> optionNone.isEmpty
res11: Boolean = true
//但可以用getOrElse()方法,当Option里面有东西的时候,就返回那个东西,如果没有东西,就返回getOrElse()的参数的内容
scala> optionNone.getOrElse("this is null") //里面没东西
res12: String = this is null
scala> optionInt.getOrElse("this is null") //里面有东西
res15: Any = 1

再说Map,Map里面的value的类型并不是你赋的那个数据类型,而是Option。即Map(key -> Option,key1 -> Option)。

scala> val map = Map("test1" -> 1,"test2" -> 2)
map: scala.collection.immutable.Map[String,Int] = Map(test1 -> 1, test2 -> 2)

scala> map.get("test1")
res16: Option[Int] = Some(1)

scala> map.get("test3")
res17: Option[Int] = None

scala> map.get("test3").getOrElse("this is null")
res18: Any = this is null

这样的好处是什么呢?还记得在java里面,每次都得为java为空的情况做判断的痛苦吗。在scala,这些烦恼通通不存在。有了Option,妈妈再也不用担心NullPointerException啦。

1.4 常用函数组合子

匿名函数

将集合的函数组合子,那肯定得先将匿名函数。如果有用过python中的lambda表达式,那应该就很了解这种方式了。

前面说到,在scala中,函数就是对象,匿名函数也是函数。举个简单的例子:

//创建一个匿名函数
scala> val addOne = (x: Int) => x + 1
addOne: (Int) => Int = <function1>

scala> addOne(1)
res4: Int = 2

注意,函数里面是可以不用return的,最后面的那个x+1就是匿名函数的返回值了。

map,reduce

因为hadoop的出现,MapReduce都被说烂了。虽然Hadoop的Mapreduce起源自函数式的map和reduce,但两者其实是不一样的。感兴趣的可以看看我之前写过的一篇:从分治算法到 Hadoop MapReduce

函数式里面的map呢,粗略的说,其实相当一个有返回值的for循环。

scala> val list = List(1,2,3)
list: List[Int] = List(1, 2, 3)

scala> list.map(_ + 1) //这里的(_+1)其实就是一个匿名函数 //让List中每一个元素+1,并返回
res29: List[Int] = List(2, 3, 4)

至于reduce呢,也是像for循环,只是不像map每次循环是当前元素,reduce除了当前元素,还有上一次循环的结果,还是看看例子吧:

scala> list.reduce((i,j) => i + j) //两个两个一起循环,这里是让两个相加
res28: Int = 6

比如上面的例子,有1,2,3三个数。第一次循环的两个是1,2,1+2就等于3,第二次循环就是上次的结果3和原本的3,3+3等于6,结果就是6。

filter

filter故名思意,就是过滤的意思,可以在filter中传进去一个匿名函数,返回布尔值。返回true的则保留,返回false的丢弃。

scala> val numbers = List(1, 2, 3, 4)
numbers: List[Int] = List(1, 2, 3, 4)

//过滤出余2等于0的
scala> numbers.filter((i: Int) => i % 2 == 0)
res0: List[Int] = List(2, 4)

foldLeft

这个和reduce类似,也是遍历,除了当前元素,还有上一次迭代的结果。区别在于foldLeft有一个初始值。

scala> val numbers = List(1, 2, 3, 4)
numbers: List[Int] = List(1, 2, 3, 4)

//(m: Int, n: Int) => m + n这部分是一个匿名函数
scala> numbers.foldLeft(0)((m: Int, n: Int) => m + n)
res30: Int = 10

二.scala函数

在最前面有介绍到,函数就是对象。那为什么函数能直接运行呢?这其实得益于object的apply这个语法糖。

偏函数

偏函数(PartialFunction),从某种意义上来说,偏函数也是scala中挺重要的一个语法糖。

偏函数它长这样:

PartialFunction[A, B] //接收一个A类型的参数,返回B类型的参数

值得一提的是,scala中有一个关键字case,就是使用偏函数。继续举例子:

//下面的case就是一个偏函数PartialFunction[Int, String]
scala> val one: PartialFunction[Int, String] = { case 1 => "one" }
one: PartialFunction[Int,String] = <function1>

scala> one(1)
res11: String = Int

scala> one("one")
<console>:13: error: type mismatch;
 found : String("one")
 required: Int
 one("one")

case关键字会匹配符合条件的类型或值,如果不符合,会报错。内部实现就不介绍了,知道是个常用语法糖就好。

而这个case关键字也正是实现scala模式匹配中,必不可少的一环,有兴趣的童鞋可以看看我这篇:scala模式匹配详细解析

这里再说一个常见应用吧,函数组合子中有一个collect,它需要的参数就是一个偏函数。下面看个有意思的例子:

//这个list里面的Any类型
scala> val list:List[Any] = List(1, 3, 5, "seven")
list: List[Any] = List(1, 3, 5, seven)

//使用map会报错,因为map接收的参数是普通函
scala> list.map { case i: Int => i + 1 }
scala.MatchError: seven (of class java.lang.String)
 at $anonfun$1.apply(<console>:13)
 at $anonfun$1.apply(<console>:13)
 at scala.collection.immutable.List.map(List.scala:277)
 ... 32 elided

 //但如果用collect函数就可以,因为collect接收的参数是偏函数,它会自动使用偏函数的一些特性,所以可以自动过滤掉不符合的数据类型
scala> list.collect { case i: Int => i + 1 }
res15: List[Int] = List(2, 4, 6)

因为collect接收的参数是偏函数,它会自动使用偏函数的特性,自动过滤不符合的数据类型,而map就做不到。

部分应用函数

所谓部分应用的意思,就是说,当调用一个函数时,可以仅传递一部分参数。而这样会生成一个新的函数,来个实例看看吧:

//定义一个打印两个输出参数的函数
scala> def partial(i:Int,j:Int) : Unit = {
 | println(i)
 | println(j)
 | }
partial: (i: Int,j: Int)Unit

//赋一个值给上面那个函数,另一个参数不赋值,生成一个新的函数
scala> val partialFun = partial(5,_:Int)
partialFun: Int => Unit = <function1>

//只要一个参数就可以调用啦
scala> partialFun(10)
5
10

部分应用函数,主要是作用是代码复用,同时也能够增加一定的代码可读性。

当然还有更多有意思的用法,后面有机会说到再说。

函数柯里化

刚开始,听到柯里化的时候很奇怪。柯里?啥玩意?

后来才知道,其实柯里是从curry音译过来的,这个是个人名,就是发明了柯里化的发明人。

柯里化是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。

看看具体是怎么使用吧:

//我们知道函数可以这样定义,让它接收两个参数
scala> def curring(i:Int)(j: Int): Boolean = {false}
curring: (i: Int)(j: Int)Boolean

//可以把这个函数赋值给一个变量,注意变量的类型
scala> val curringVal:(Int => (Int => Boolean)) = curring _
curringVal: Int => (Int => Boolean) = <function1>

//可以让这个变量接收一个参数,又变成另一个函数了
scala> val curringVal_1 = curringVal(5)
curringVal_1: Int => Boolean = <function1>

//再用这个变量接收一个参数,终于能返回结果了
scala> curringVal_1(10)
res32: Boolean = false

柯里化其实是把一个函数变成一个调用链的过程,和上面的部分应用函数看起来有点像。

这几个部分初次看可能不知道它究竟有什么用,其实这些功能的一个主要用途是函数式的依赖注入。通过这部分技术可以把被依赖的函数以参数的形式传递给上层函数。限于篇幅这里就先省略,后面再介绍。

结语:

此次介绍的是scala集合的一些内容,以及一些函数的特性,再说一遍,函数其实就是对象。

我一直有一种观点,在学习新的东西的时候,一些偏固定规则的东西,比如语法。不用全部记熟,只要知道大概原理,有个映像就行。

比如说scala的函数式编程,或是java的OOP,不需要抱有先把语法学完,再学习相关的编程理念,这在我看来是有点本末倒置了。

我一般的做法,是先熟悉大概的语法,然后去学习语言的精髓。当碰到不懂的时候,再反过来查询具体的语法,有了目标之后,语法反而变得不是那么枯燥了。

以上只是我的一些个人看法,那么本篇到此就结束了。

到此这篇关于Scala函数式编程专题--scala集合和函数的文章就介绍到这了,更多相关scala函数式编程内容请搜索自学编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持自学编程网!

编程技巧