编程即是编制对数据进行运算的过程。特殊的运算必须用特定的数据结构来支持有效运算。如果没有数据结构的支持,我们就只能为每条数据申明一个内存地址了,然后使用这些地址来操作这些数据,也就是我们熟悉的申明变量再对变量进行读写这个过程了。试想想如果没有数据结构,那我们要申明多少个变量呢。所以说,数据结构是任何编程不可缺少的元素。
泛函编程使用泛函数据结构(Functional Data Structure)来支持泛函程序。泛函数据结构的特点是”不可变特性“(Immutability), 是泛函编程中函数组合(composition)的必需。所以,与其它编程范畴不同,泛函编程的泛函数据结构必须具体一套特定的数据运算方式。
泛函数据结构及运算方法具备以下特征:
1、不可变特性(Immutable)
2、运算在数据结构内进行。尽量避免使用中间变量
3、运算返回新的数据结构作为结果
我们先看看熟悉的OOP数据运算风格:
scala> var arr = Array(1,2,3)
arr: Array[Int] = Array(1, 2, 3)
scala> var sum = arr(0)+arr(1)+arr(2)
sum: Int = 6
以上运算是需要中间变量的。而且是在结构外进行的:先把数据从地址读出再相加。
scala> arr(0) = sum scala> arr
res9: Array[Int] = Array(6, 2, 3)
直接赋值后arr内容变了。在这里arr是“可变的”(Mutable)数据结构。肯定的是如果下面需要再次使用arr时,我们是无法保证它内容一致性的。
再看看泛函风格:
scala> val arr = Array(1,2,3)
arr: Array[Int] = Array(1, 2, 3)
scala> val sum = arr.sum
sum: Int = 6
泛函运算直接在数据结构内进行,不需要中间变量。
scala> val arr1 = arr map { x => if(x == 1) sum else x }
arr1: Array[Int] = Array(6, 2, 3) scala> arr
res10: Array[Int] = Array(1, 2, 3)
arr1是赋值后新的数据结构。arr没有变化。这样我们可以放心使用arr来进行函数组合了。
可能这里会出现一些误解:arr1先复制了arr内的数据后再修改内容,所以arr没有变。这样理解有对也有不对:从效果来说arr1是复制了arr。但从具体做法上系统只是把arr(0)下面节点的指针指向了arr1(0),并没有进行实质的数据复制。