函数式编程的知识前置
在我们学习函数式编程的时候,lambda是一个经常出现的名词。原因是函数式编程背后的逻辑思维来自于lambda演算。看上去非常高大,不过其实也是比较简单的。我这里不想解释lambda演算,但是我建议把lambda演算作为你学习函数式编程的前置知识。原因是学习后,你对一切皆函数(函数是一等公民),惰性求值等概念会有更好的理解。这些只需要入门即可。科里化、类型论则是更深的理论知识,能帮你熟练运用函数式编程的工具。范畴论什么的就可以帮你吹那啥了。 总之,有一定的数学理论基础可以帮你更好的理解函数式编程里的各种魔法。如果没有,其实也能用的很好,这个看个人。我就属于理论会点皮毛,但是也够忽悠大多数人了。我在我们公司做了四期的函数式编程的入门,基本上我讲错了,也不会有人反驳,我自己也在学习,有时候上一期的错误要我自己在下一期去更正。但是,好多时候你理论不对,不代表你写的代码不对。前置知识建议大家有先了解。我暂时不会聊这些
OO(object oriented,面向对象)是抽象数据,FP(functional programming,函数式编程)是抽象行为。
这是来自于《On Java 8》中极为准确的描述。On Java 8我很久以前也看过,完全不记得有这么一句话。多年后,当我实践了部分Java FP编程后,同时也入门学习了lambda演算后。我再看到这句话的时候,一瞬间,它击中了我。我终于明白FP编程并不是语法糖,而是另外一种编程范式的原因了。
从简单的开始
如果我们现在有这么一个函数:
f(x) = x+1
这个函数其实大家应该能理解,当它实际计算的时候,比如计算x=2或者3的时候,它是这个样子的
f(2) = 2+1
f(3) = 3+1
这里隐含的就有三个地方可以定义,我们传入什么,如何运算,返回什么?在f(x) = x+1中,我们传入一个数字,进行+1运算,返回一个数字。所以Java中我们就需要进行如下定义:
Function<传入参数,返回参数> = 传入参数+1;
实际上我们是这么写的
Function<Integer,Integer> a = (x)->x+1;
和
f(x) = x+1
比较,你会发现如此相似。
如果我们把Function<Integer,Integer> a = 替换成f,=替换成->,几乎一模一样。那么为什么Java会这么复杂呢,首先Function<Integer,Integer> a实际上是在定义一个变量,它表示我要创建一个函数,之后如果你们要用,你们就必须使用a去引用它,同时这个函数会吃进去一个Integer,吐出来一个Integer。a则指向了(x)->x+1这个运算规则。但是由于表达a指向了(x)->x+1已经使用了=号了。所以真实的等于号,我们只能使用另外一个符号,我们选择了->。这里就是函数式编程的第一个入门门槛,符号的含义发生了转变。实际上大部分编程语言都是这样的问题。=优先被其它行为占用了(通常是引用),当我们想使用函数中的相等时,只能另寻符号。
接下来我们再回过头看Java代码的定义: Function<Integer,Integer>用来定义参数类型,本身表示它支持一个入参一个回参的函数。我们学的数学不需要,至少高中数学范围内,我们基本讲的是都是数字类型。所以不是它不存在,而是在我们学习数学的上下文中,这部分是默认的。a = 创建了一个引用。这是由于我们需要在别处调用,类似于学习中的某某定理。代码中如果你要复用,需要起个名字。剩下(x)->x+1 很明显对比f(x) = x+1。f可以忽略,我们需要为= 另找一个符号,于是有了->。
好,我们再观察一段代码
list.stream().map(x->x+1).toList();
这里的x->x+1为什么又不需要Function<Integer,Integer> a = 定义了呢?首先map只接受Function,所以Function是不言自明的,Function包装的第一个类型,是基于上文推断的,这里的上文是list.stream(),所以第一个类型就是明确的。第二个类型也是明确的,上文和"1"运算后第二个类型同样是明确的。剩下一个a=。我这个运算逻辑又不用复用。所以这个函数,就可以直接写成x->x+"1"。
部分关于Java函数式编程的文章,对函数式编程的判断就到此为止了。基本上就下了结论。函数式编程好,匿名函数省代码量。
到目前为止,函数式编程确实像是一个语法糖。不过我们要理解,这种简洁来自于两方面:
第一:类型可以不用定义,因为它可以通过上下文推断;
第二:定理名(函数名)可以不用定义,因为它不通用;
待续。。。