前言
熟悉pointfree我认为是很好的一件事情,也是编程中的重要的风格之一。因为它可以帮助你在正确的抽象层次上进行思考。基于这个初衷写了这片文章。
如果你学会了这种编程风格,并不是说就应该在任何地方都采用pointfree。我更希望你以一种拓展思维的角度去看这个事情。
什么是pointfree
Pointfree这个概念,在编程中叫做隐式编程,同时也叫做无点编程(point-free),其概念是函数定义不用标示所需要操作的参数(点)的一种编程范式。
pointfree 则是将函数与函数之间以无点的方式组合起来的思想。这里的复合方式比如compose、sequence,pipe等方式。
所以在严格使用复合模式使用的时候,非常适配于方程式推理。可以先想像成可以组合的数学公式。并且很神奇的依然可以套用我们数学中的一些定理。
使用pointfree的前提
在函数式编程中使用pointfree的前提是纯函数之间,至少说“建议”是纯函数,如果你可以把副作用控制的足够好的话也是可以的。
结合上一篇文章中的内容,我们可知函数是没有副作用的 - 每当输入参数一样时函数返回值是恒定一致的。
那么这个时候,我们面临着如何更加优雅的组合纯函数的这个问题
pointfree解决了什么问题
先看一个命令式的例子:
可以先看到上面抽离出来的三个纯函数,抽离的非常不错,就像我们上一篇文章说的,我们需要更多这样的纯函数,具有非常好的确定性。
operate过程操作一组数据 a, b。在operate内部写了三个命令,先进行相乘,然后乘积自增1,最后自乘。operate过程会返回最后的结果squared。
我相信这是谁都写过的代码,这也是我们项目代码组成的基本缩影。首先说明,这种写法是没有问题的,不但没问题,如果你像上面的例子一样:该抽离的函数抽离,命令过程一步一步的很清晰,那么最终效果是非常好的。
既然已经那么好了,我们这里是要解决什么问题呢?--- 内存开辟问题。
我们可以看到上放例子,operate过程里面有三条指令,每条指令运行的结果都赋值给了一个变量。声明变量的话系统会单独开辟一块内存来存储,而且存储空间是跟数据量成正比的。那么我们可不可以避免这三个变量的声明呢?
诶,这里有人可能有疑问:“我可以一个变量都不声明,我完全可以这么写”。
这样写是可以计算出相同的结果,但是这样写首先就违反了命令式的初衷,没有简明的体现出执行步骤,代码不清晰,不易懂,扩展性还不如上方的例子。如果添加更多的过程,数括号都要数一段时间。
这个时候,一些函数工具解决了我们这个问题,下面将通过一些现有的javascript函数库中的函数来讲几个例子。
pointfree的实现方式有哪些
这里先介绍两个库
- Ramda 非常实用的javascript函数库
- lodash-fp 柯里化的lodash库
R.pipe
Ramda.pipe 【汉译pipe: 管道】
先看一下pipe如何执行我们的上方例子:
如果对比看一下,pipe的代码实现方式非常的简洁,执行过程依然清晰,从左往右执行。而且我们可以很方便的修改顺序以达到修改执行过程的目的。
注意: pipe 函数的结果不是自动柯里化的。
Node.js中的pipe
在Node.js中也有一个函数叫做pipe,在Node.js中pipe函数负责处理流数据(stream),例如
pipe前面是读取流,后面是写入流。在实际项目中我们在Nodejs发起请求的时候,request则为读取流,response为写入流。
这里还是补充一下request场景下的pipe代码,以方便理解下方为何pipe可以避免无用“点”的增加:
一个读写流通过pipe处理之后看上去十分简洁、利落。
Node.js中的pipe的作用根我们要说的pointfree中的无点概念很像,大家可以想一下,一个接收流的过程,我们往往需要定义一个变量,当数据量很大的时候,系统需要随之相对应的开辟较大的内存,为了更优雅的处理这样的场景,就用到了pipe去进行读写流操作。
当Node.js想用一个管道函数去达到消除变量的目的的时候,这个思想根函数式编程中的“无点”想法非常一致。都是打算通过管道思想去消除“点”。
如果你对Node.js的pipe感兴趣的话,有位作者还写了一个可控制读写流速度的pipe,附上传送门Nodejs中流的操作,实现简单的pipe
R.compose
Ramda.compose 【汉译compose:组成】
我们先来看下compose如何优雅的实现上面的例子。
执行顺序变了,pipe执行顺序是从左到右,compose的执行顺序是从右至左。从执行方式和结果上来看,这两种方法除了执行顺序的不同,其它都一样。
比较官方的说法是compose针对的是嵌套执行的场景:
其时看过compose源码就会发现,把arguments顺序reverse后顺序就可以反转,但是为什么没有这么做呢?这块比较有意思:
作者认为上方嵌套执行场景为“右倾”,为了解决右倾,compose选择了“左倾”,并认为“左倾”更加能够反映数学上的含义。
从右至左执行符合传统嵌套的阅读顺序,因为你是由外往内读代码的。square -> addOne -> multiply。我认为大可不必深究这个问题,可以根据你主观的判断来选择使用compose或者pipe。如果你知道他们之间的具体使用区别,欢迎在评论区留言。
Redux中的compose
用过Redux状态管理的同学应该都知道,在Redux为数不多的6个API中,有一个compose。Redux中的compose一般用法如下。
1、增强store(官方)
2、装饰组件 (非官方)
上方两个场景中,compose内部也是pointfree,消除了重复的“点”,达到组合的目的,依然简洁明了。
那么问题来了,Redux中的compose与Ramda中的compose有什么区别?
答:没有区别。
Redux就是原封不动的把人家函数库的compose方法放到了自己的API中,原因是: 因为方便...没有错,就是因为方便。你可见compose这个方法那是相当的好用了。redux只有2kB(包括依赖),可是它依然把compose方法放到源码中,你可见comopse的重要性。
我打算以后的写等SDK等也借鉴一下,以防你没有compose,我自带一个给你用,用了我的compose使得我的api调用更加简洁。
Diy
也完全可以自己进行组合,不依赖第三方库。只需要符合pointfree风格即可。
pointfree的特性有哪些
严谨来说是管道模型或者洋葱模型的特性,因为考虑到一个问题,就像上面提到的,其实是可以通过嵌套方式来实现pointfree风格的。是因为那种方式不易读等缺点被列为非主流写法。所以在整理特点的时候偏向于管道模型。函数式编程的模型后面单独写一篇文章聊一聊。
易读
通过上面立举的嵌套模型和pointfree的例子可以清晰的看到,pointfree的执行过程是非常易读的。其实在编程语言中也一直在避免嵌套的情况。比如回调地狱,promise的链式调用,条件语句嵌套等等,我们都会想一些相应的解决方法来解决嵌套的情形,使得代码更加简洁。
易调试
很久以前我看见有人在质疑函数式编程的调试很难,其实恰恰相反,pointfree的调试非常方便,甚至可以称为它的优点之一。
在这里要复习一下我在《聊一聊函数式编程中的数学》中写到的单位元概念。我们可以利用单位元在pointfree中进行调试。
换句话说,在数学中,恒等函数为函数f(x) = xf(x)=x,输入等于输出。
我们依然用上方pointfree的例子,来看一下如何打印日志。
上述print函数可以插入你想要插入的任何位置。很方便的在执行过程中进行调试。
当然Ramda也提供了单位元函数:Ramda.Tap
如下使用:
方程推理
在第一段中提到过这样一句话,复合模式使用的时候,非常适配于方程式推理,类似于数学公式的组合。
形式上,一个在集合S上的二元运算 被称之为可结合的若其满足下面的结合律。
pointfree
如果对这方面感兴趣可以看我以前写的一篇文章《聊一聊函数式编程中的数学》
复用性
我们放心的去“拼接”函数,就像拼接乐高模型,无论怎么拼接,他始终都是乐高,不会拼成其它材质的东西。纯函数之间的拼接后,依然是纯函数。
所以在不同的使用场景中,我们可以选择不一样的“零件”进行组合。纯函数的特性也让复用性大大提升,你完全可以放心使用拆解和组合出来的函数。
易扩展
就像上面我们可以在组合执行过程中插入R.tap打断点一样,我们不光可以在中间打断点,还可以插入一些辅助函数帮助我们更好的处理计算逻辑。
类似Either,safeProps,Maybe,IO等等,很方便的进行扩展。
END
从搭建一栋建筑角度来说,并不是整个建筑都用一种材料才是合适的,哪里该用钢筋,哪里该用砖头都是需要合理搭配的。我们该抽离成pointfree风格使用,还是使用命令式过程,都需要去合理的搭配。
这种风格会延伸出来两种模型 管道模型、洋葱模型,下一篇文章来聊一聊。中间可能有一些Flutter和小程序底层架构的文章可能会在中间穿插。大前端方面的知识体系也积累蛮多,打算沉淀文字一波。
?
往期文章推荐
- 《聊一聊函数式编程中的副作用概念》
- 《聊一聊函数式编程中的Hindley-Milner》
- 《聊一聊函数式编程中的数学》
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!