Hello Mr.J——函授。呃。函数式编程

从12月开始吧,就一直在看函数式编程相关的东西,大部分时间走了一些弯路,因为这里面有很多概念不容易理解,那么这一个月的时间也是总结了一下,发现有一本师哥给的英文书写的也是在经过一些弯路学习过程后给出了简单易理解的解释。那么这篇文章就不谈什么实现细节,用java的一些简单概念,来解释这些“难以理解”的概念。
并不是什么新的语法 函数式编程并不是什么新的东西,可以说当年阿隆佐·邱奇(Alonzo Church)老爷子提出了lambda演算,这就算是函数式编程的概念雏形了,而到了1958年第一个实现这个概念的编程语言Lisp就诞生了。限于当时计算机的硬件条件,函数式编程的给人们带来了执行效率低下,空间浪费严重的印象,从而导致一直被冷落。
时至今日,计算机硬件飞速发展,硬件的限制已经可以忽略不计,而随着Haskell,Python,Scala,Java等等语言的支持与优化,函数式编程在大数据和人工智能等场景下展露它的威力。
函数式编程并不是一种语法,而是一种编程范式,简单来说就是一套规范、接口。能实现它规范的都可以算是函数式编程。
核心是纯函数 在学习和查阅资料的过程中,经常会看到有人评论函数式编程的相关概念难以理解,一开始我也是这样,在老师的提点下换了一个角度看这个东西,函数式编程的一些难点就都顺理成章了。
函数式编程的核心是数学上的纯函数,也就是f(x),什么无副作用啊,什么高阶函数啊,什么函数一等公民啊,全都是数学上的f(x)带来的自然而然的东西,并不是什么这种规范带来的新东西。
那么数学上的f(x)又是什么呢,对于 y = f(x) 来说,函数相当于建立了一个从集合x到集合y的一一映射,并且同样的x一定会映射到同样的y,换到编程语言上来说,就是输入参数和输出结果的映射。
不知道用来忽悠谁的概念 【Hello Mr.J——函授。呃。函数式编程】好了,既然函数式编程就是用代码写f(x),并且已经知道了函数式编程的核心是纯函数那么接下来就好办了,来看一下函数式编程里面让人头疼的概念。
一等公民 首先在英文的资料里面,说的是first class citizen,当然这个前提是写文章的人把编程范式比作一个王国,赋予了各种概念权利,在函数式编程的王国中,函数这个概念有权利出现在任何位置,可以担任所有的角色,由此而来了一等公民的说法。
说法本身没什么问题,就是忽略了一些前提。而我在看完之后的出的结论是这个:
函数式编程中,一切皆函数。
用几个简单地代码来表示大伙一看就懂了
int 5 :f(x) = 5
a + b : f(x) = a + x,x = b
funcA(c ->{c + d })(函数作为参数) :f(g(x)),x = d
无副作用 很多的文章将无状态转移,无副作用作为函数式编程的特点,副作用在一般的定义来说就是IO操作,数据库操作,变量的值更改等等,而为什么只在函数式编程里面称作为副作用呢?这个很奇怪,因为在命令式编程中就专门做这些操作的。
这里我的理解是函数式编程,每一个函数都只做映射,而f(x)执行之前和执行之后并不引起外部变量,或者数据库,或者缓存的改变,因为一旦改变了这些东西,可能会影响其他的函数,第一次输入x给出y,第二次输入x则给出了z,导致破坏了数学上f(x)的定义。
引用透明 用代码来看的话,就变得很简单了。


int result = funcA(1,2,3)


假设在代码中调用了funcA输入x,y,z的入参(1,2,3),返回了结果6,那么把这个代码删掉直接在result这里赋值6,程序一样能执行就说明是引用透明的
Partial Function(偏函数)AND Partially Applied Function (部分施用) 偏函数是数学上的概念,是只对函数定义域的一个子集进行定义的函数,如果一定要找一个代码上的说法的话,类似于java的重载。而用数学例子来看的话,分段函数比较通俗易懂,当然数学上偏函数的概念不止于此,更加深层次的东西我也还没看懂。
f(x) = x(x > 0)
f(x) = -x(x < 0)
f(x) = 0(x = 0)
为什么和部分施用放到一起来说呢,大家可以看这两个概念的英文写法,一般来说都是中文容易混淆,这个是英文容易混淆,懂英语的大家应该可以看出一个partial形容词,一个Partially副词。
感谢翻译这两个概念的前辈,没有把这俩也翻译成容易混淆的中文,至于部分施用,我理解他是计算机的概念。Partially Applied Function写全的话我猜是Function‘s parameters is applied partially ,函数的参数已经设置了一部分。
对应到编程的概念中比较好理解,就是部分参数设置了默认值
func(int a,boolean b)
val funcApplied = func(int a, false)
λ表达式 lambda演算看上去很复杂,如果你看过wiki上的定义的话,里面甚至还有 α 变化, β 归约,η 化简等数学概念,而在学习的过程中,为了理解这些数学概念,我还不得不去看了数学的知识,当然结果就是越看越自闭。。
为什么看不懂呢,是因为看的角度不对,从演算的概念中跳出来,我从历史上看了一下lambda演算的出现,发现这个只是邱奇老爷子只是想用另一种简单地写法来代替重复定义f(x),g(x),n(x),b(x)…就写一个符号,然后用严谨的逻辑表示:这个符号就表示一个函数。
从编程的角度看,lambda演算是匿名函数的另一种说法,也是因为这种演算方法,函数式编程中的函数才算是一等公民,能出现在各个地方。
闭包 和 高阶函数 高阶函数的概念很简单,和数学上是相似的,可以看做是函数的嵌套,也就是f(g(x)),这里就不多说了,而高阶函数引出的一个概念就是闭包了。
很多文章中都会写到闭包都会写一个具体的实现,而很少能看到直接说明闭包是什么的。
而我在看到了这么多例子之后,想到了一个简单点的理解。
假设 f(x) = π x^2/ e,那么写代码的时候,输入参数一定是x,输出结果是计算的结果,那么对于这个方法来说,π,2,e都是绑定到这个函数中的,而且外部无法访问。这就是一种闭包,强行对比一波的话,我觉得和封装差不多,一个封装的是类,一个封装的是函数。
而有了闭包这种结构之后,函数返回一个闭包,或者参数是一个闭包就好理解了,就跟一个类一样。
因为有了那些看不懂的概念而带来的好处 组合 在函数式编程中,经常可以看到的方法就是a.plus(1).minus(2)类似于一种pipeline的写法,也通俗的可以叫“连点”。这种方式带来的好处是比较明显的代码精简
天生分布,面向并发 这个也比较好理解,因为每个函数式无副作用的,在一个函数不依赖另一个函数的计算结果的情况下,可以将几个函数分布式计算,然后汇总一个统一的结果。而又由于函数式编程中比较特色的变量定义,那么就不存在线程安全的问题,随他怎么并发,反正都不能改变。
缓求值 这个可以看做是引用透明这种概念带来的好处,在调用函数的时候直接跳过,直到真正使用函数计算结果的代码才开始计算函数结果,这样就是缓求值了。
还是用上面那个例子来看,到funcA这一行的时候,计算不计算result的值对整个代码没有任何影响,直到最后一行输出的时候,才真正的需要这个result值,这个时候求值输出也是一样的。


int result = funcA(1,2,3)


System.out.println(result)
不知道该写啥,但是看别人都有,我也写写吧的总结 总的来说,函数式编程倒不如说他是数学式编程,将很多数学概念落地,并且在此基础上进行了优化和拓展,并不是java,C++有什么不好,而是有些情况下,Python,Scala更合适。
函数式编程适用场景也不在数据处理等方面,那是副作用,它的专长在于计算。

    推荐阅读