函数式编程入门例子
https://www.zhihu.com/question/364326961
函数式编程中的函数的实现不是还是用命令式的编程吗?只是核心逻辑中暴露的代码简洁了,我哪里理解错了呢?
作者:亡灵之猫
链接:https://www.zhihu.com/question/364326961/answer/962855568
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
呃,猫个人觉得题主的疑惑是因为编程语言造成的,现在的主流编程语言很少有纯函数式的,多半都是混搭,也就是说一种语言既可以描述命令式编程的思想也可以描述函数式编程思想。那么同样是阅读代码,用命令式编程的思想去阅读函数式编程思想开发的代码就会对对方的脑回路产生很多质疑;同样的用函数式编程思想去阅读命令式思想开发的代码就会遇到一大堆矛盾。应该说题主并不是哪里理解错了,而是没有真正的理解函数式编程,只看到了代码的表象,并且还在继续使用命令式编程的思考方式去解读代码。函数式编程它所使用的抽象工具和命令式编程是截然不同的,所以同一种语言里,函数式编程的代码和命令式编程对代码的解读方式也是不一样的。下面的例子主要使用JS语法,为了保持中性和简化书写,省略了let、var这类变量声明一个栗子:a = 1;
b = 2;
c = a + b;
函数式编程者的解读(伪代码):/* 首先函数式编程没有变量,赋值实际是一种“符号绑定“,是一种别名
- a绑定1意味着a就是1,1就是a
*/
a => 1
b => 2
/* 算术表达式也是一种函数, 1 + 2 就是设一个加法函数 add 将1和2作为输入,它必然返回函数3
- 这和数学上的函数是一致的,输入端输入各种数值,输出端取得和数
- 所以下面我们只是认为c绑定了 add(a(), b()) 这种形式
- 这里可以有两种解读:
- 一种情况是化简,既然 add(a(), b()) = 3,那么 c() => 3, 这叫做“立即求值“
- 另一种情况,把 add(a(), b()) 视作一个新的函数,不使用的时候就不关心它的结果,
- 这叫做“惰性求值“
- 命令式编程极少使用惰性求值,实现也不方便,但是函数式编程则一般情况下喜欢从惰性求值角度去
- 思考问题
*/
c() => add(a, b)
再看一个栗子:if (x == true){
y = 1;
} else {
y = 10;
}
///////////////////////
// 函数式编程的开发者更喜欢这样写
y = x ? 1 : 10;
// 甚至干脆这样写
y = (x) => x ? 1 : 10;
函数式编程者的解读(伪代码):/* 函数式编程并不存在“语句”这种概念,所以上面if要被重新抽象为逻辑表达式
- 好在他比较简单,只是和y相关,因此可以看作对y的一种函数式推导
- 即存在一个求真值表的函数(用 cond 表示),第一参数为“真“则输出第二参数,
- 第一参数为假则输出第三参数。
- 这个样子其实也就是传统的三目元表达式,函数式编程开发者可能喜欢用三目元表达式去看待问题
- 因为 if 是语句,是从某些状态到一系列动作的二部曲,对函数式的抽象很不友好,需要在思想
- 上做很多转换
- 三目元是“表达式“,它是单一、孤立且完整的,有确定的输入和确定的返回值
- 理想的函数式编程只有表达式,没有语句,不存在先决条件和后续动作之间的关联
/
y => cond(x(), 1, 10)
再说一个互换角度的栗子:// 这是一个典型的命令式编程
// 要求 a + b 必须小于 100,如果不满足条件,则取较小的一个数,
// 如果较小那个数还大于100,则返回0
(a, b) => {
g = a + b;
if (g < 100) return g;
if (a > b) c = b;
else c = a;
if (c >= 100) return 0;
return c;
}
上面虽然书写完全是命令式编程,但是由于内逻辑是封闭的,没有和外部状态纠缠,因此可以被转化为函数式编程思想去解读(伪代码):/ 可以接着使用上面用过的工具进行抽象 - 大于、小于对比也是函数,这里用 lt 表示小于对比,gteq表示大于等于对比
- 尽管增加了很多条件分支,但是实际上就是一阶一阶往上堆函数而已
*/
(a, b) =>
((g) => cond(lt(g, 100),
() => g, // 这里返回的是函数, a + b < 100 时返回一个能返回 a + b 结果的函数
() => ( //否则返回另一个函数,这个函数要用另一个函数来推导
(c) => cond(gteq(c, 100), 0, c) //输入条件大于等于100,就返回0
)(cond(gt(a, b), b, a)) //先取出较小的数来作为推导条件
)(add(a,b))()
这个例子已经开始把函数作为一等公民来对待,思考方式也开始要和命令式编程区别对待,否则很难真正理解代码。// 函数式编程思想写出的与上面命令式编程等效的代码
// 强行用命令式编程的思考方式看是很奇怪的,但是通过上面的解析就很清楚了
(a, b) =>
((g) => g < 100 ?
() => g :
() => ((c) => c >= 100 ? 0 : c)(a > b ? b : a)
)(a + b)()
总之,函数式编程和命令式编程在思考方式上是完全不同的,尽管他们都可以用来描述相同的业务,可以使用相同的语言来书写。但是不能用命令式编程的思考方式硬套,必须学会函数式编程的思考方式,并用函数式编程的思考方式去解读代码。同样的代码在不同的抽象思维下会产生不同的解读,函数式思维下写出的代码在命令式编程的解读下看是奇怪的,同样命令式编程写出的代码在函数式编程的解读下是矛盾的,这就像谁更“优雅”的争议,角度不同是没有标准答案的,只有正确认识到视角的不同,并且不断切换角度才能找到合适的切入点。两种编程方式他们源于两种不同的抽象思维,函数式编程不是用命令式编程“描述”出来的,同样,函数式编程也不是命令式编程的上层抽象,必须把这两种东西分开看待。最后,现在大家主流使用的计算机骨子里是寄存器和指令,亲和于命令式编程,函数式编程是这些计算机上的客人,也是最近20年来计算机性能进步飞快,多核心还有异构计算兴起函数式编程才逐渐受到重视。因此人们在理解函数式编程时很容易绕到底层,用命令式编程的习惯强行解读函数式编程的“实现”。但是函数式编程是一种思想,命令式编程也是一种思想,他们是平行的,都有各自的知识体系,这些知识体系也都在底层都有各自的“实现”,并没有真正的从属关系,是需要分别学习的。个人也不主张过于洁癖的追求“纯”函数式编程,因为开发者需要付出更大的代价去进行抽象,运行效率也更难提升;但是如果要利用起函数式编程高超的抽象技巧和无副作用的爽快特性,就必须要切换思维方式,把它和命令式编程区别开来单独学习,需要单独进行大量的训练,用命令式的思维去生啃代码是无法真正理解函数式编程的,也没法在两者混搭时构筑安全的边界。函数式编程有它工程上的优势,函数式抽象不关注上下文逻辑,代码之间相互的污染是可以完全杜绝的,这是命令式编程做不到的。有了函数式编程的抽象能力,开发将会比使用单纯的命令式编程增加很多非常厉害的思想工具,也更容易写出高质量的代码。编辑于 2020-01-05赞同 33收起评论分享收藏喜欢收起10 条评论切换为时间排序人生如逆旅 (提问者) 8 天前非常感谢答主,答主说的挺对的,我刚刚开始接触函数式编程,以前只是单纯的搬Java8的Stream相关API,自以为掌握了(汗颜),直到最近刚开始实习的时候想把公司对于数据处理的这块逻辑用Lambda重新写一遍,这样逻辑会清晰很多,所以就重新看了Java8实战这本书,但深入下去之后发现,Java8实现的函数式编程本质上还是使用匿名类的方式,并且是对Java7及之前的代码进行了包装,所以会产生这个疑问,即Java8为什么要这么做,因为本质是一样的呀,所以为什么会有函数式编程与命令式编程之分呢?看完答主的答案我越发觉得自己需要去系统的学习一下函数式编程,因为思维可能一直在用命令式编程的思维去思考,非常感谢,阿里嘎都~[赞同]赞回复踩举报亡灵之猫 (作者) 回复人生如逆旅 (提问者) 8 天前噢~Java主要是背负了沉重的历史负担,Java 8以前Java是无法直接描述函数式抽象的(缺少闭包支持),形势所迫,所以它是通过打补丁在接口上做语法糖实现的闭包。Java这样深耕工业领域语言,不会那么无聊去加一个看上去花里胡哨的语法糖,主要的问题就是,语言要承担的任务是把开发者脑子里的图纸表述成可以实现的工程结构,结果就是Java发现自己无法正确表述函数式编程开发者脑子里那张图纸,大家都被迫绕路。从这一点也就说明了问题,抽象方法是人们脑子里的图纸,语言则是描述这个抽象的工具,我们要区分的是脑子里这张图纸该怎么画,然后再去用工具来描述它;所以是先考虑用什么方法(函数式、命令式)来画图纸,然后在关心怎么使用语言来描述,至于语言底层怎么实现这其实是和语言的历史有很大关系的,他不影响我们脑子里的图纸该怎么画1回复踩举报亡灵之猫 (作者) 回复人生如逆旅 (提问者) 8 天前Python就是另一个典型,它的命令式抽象最后都会展开成函数路径,循环最终是展开成迭代器,算术计算最终是展开成对象方法,对象方法最终展开成孤立的函数。建议深入一下这个方向,换个角度会看到相当不同的世界1回复踩举报谜之枪兵X7 天前还是好奇有没有从指令集的层面就函数式的东西,或者干脆,这种东西能不能被实现,实现的话是不是非得打破冯·诺依曼架构不可赞回复踩举报亡灵之猫 (作者) 回复谜之枪兵X7 天前FPGA编程和函数式很接近,思想上有很强的互换性,不过那种编程实际的名字叫逻辑编程。函数式编程、逻辑编程这类编程又被归类为“声明(Declarative)式编程”。一些DSP、AISC芯片也可以在有限资源下采取类似函数式的指令,比如数据流编程,也就是给现在急需性能的AI、图形做优化的。但是要做成通用计算芯片,个人觉得声明式编程对物理芯片的设计并没有命令式那么友好

浙公网安备 33010602011771号