Kalec的网络日志

漂浮的灵魂

HOME 首页 TS简单函数式总结(一)

TS简单函数式总结(一)

技术
2018-06-04 17:20:00

TS简单函数式总结(一)

嘿,一个多月没和大家见面喽,上个月说的要学函数式其实也没学多少。不过倒是完成了个IoT项目,不过由于写的过于优秀,暂不开源!前后端带与硬件通信的那种,虽然就是点简简单单的类似智能家居那种。好了不扯远了,还有一个想说的就是为什么标题变成TS了,因为前面遇到了不少问题,打算今后能用TS的就尽量少或者不用js。这段时间开始,我将逐渐从函数式的最基本实现开始,大致捋一捋当中的奥秘。

纯函数

纯函数是函数式必须需要掌握的东西,其实吧,这玩意挺简单的。为啥这么说呢?因为这个早在初中的时候就已经接触过啦,其实这里的纯函数就是当时学的那种函数,数学里面那种。来回忆一下y = f(x)的特点是什么呢?
当且仅当…算了。还是嗦点容易听懂的吧,就是在定义域内任意一个x只有一个与之对应的y值。
函数输入一个值,出来的结果是不会改变的。换来咱们编程里面,如果结果确定,就能避免很多麻烦。并且由于输入不变返回的计算结果是相同的,我们还可以在这里做缓存,节约计算机性能开销。实际上数学里面的东西都是极其严谨的,要是把这种严谨性带到编程里面来,能让遵守规则的你尽可能的少些bug。
这里可能会有人有疑问了,那么在平时写的函数中有那些是结果不能一对一的呢?嗯,其实还真不少,例如周知的splice,再看一个平时很多情况都会用到的一种吧,就是函数里面拿全局变量,或者上层作用域的变量时候,函数的结果是取决于函数外的某个变量的时候,结果就不唯一了。

interface sth { <T>(s): T; } let baseNum: number = 10; const check: sth = x => 23 + x check(baseNum)

其实,仔细想想就是只要和外环境交互的都会有副作用,都会导致函数不纯。比如遇到异步网络请求不纯的时候我们怎么做呢?
我们需要的就是缓存,缓存可以把需要的东西包装好,供由命令式编程调用(传统编程方式如面向过程,面向对象),上点干货

const memoize: Function = function (f:Function) { let cache: object = {}; return function () { let arg_str: string = JSON.stringify(arguments); cache[arg_str] = cache[arg_str] || f.apply(f, arguments); return cache[arg_str]; } } const pureHttpCall: Function = memoize(function (url: string):Function { return function (){ return $.getHttp(url); } }) const getStr:Function = pureHttpCall('/test'); export default getStr;

柯里化

柯里化这里是我的痛点,好几次面试都遇到过,最初时候答得都不是太好,核心的部分并没有太清晰的理解。我印象最深的就是七牛的一个题,大概就是完成一个fn的函数,完成以下功能。

const add = (x, y, z) => x + y + z function curry (fn) { const _c = argList => argList.length === fn.length ? fn(...argList) : (...rest) => _c([...argList, ...rest]) return _c([]) } const fnAdd = curry(add) fnAdd(2, 3, 4) //9 fnAdd(2)(3, 4) //9 fnAdd(2, 3)(4) //9 fnAdd(2)(3)(4) //9

这个题那个时候虽然还是磨出来了,但那个时候确实不理解柯里化,现在转过来一看,确实明了。当中的函数中的值全部由值传递来完成,拒绝访问外部变量。之前还有过另外一种思路,很操蛋的那种,在curry函数中创建了一个变量来存值,由于闭包的关系,第一次执行当然没问题,后面再执行,就出现诡异结果。这就是函数不纯净造成的问题。
通过上面这个可以大致理解到,柯里化就是只给函数先传一部分值,让返回的函数去接收处理剩下的参数。柯里化体现出了一种函数可以预加载的能力。

组合compose

函数可以组合,这是肯定的,别忘啦曾经还有这种写法f(g(x)),而在程序中也一样,组合能让各种功能给结合起来,当然,还是有前提的,必须“遵循数学定律”。这么做的目的就好比拼积木,需要啥添加啥之类的。

export const compose: Function = function (f:Function, g:Function) { return function (x:string):any { return f(g(x)); } }

f(g(x))这个一看就明白,g的执行顺序是在f前的,这样能够建立一个从右向左的数据流,一个函数的输出为另一个函数的输入。不用compose来做组合的话,层层嵌套可读性就低了很多很多。在这里的f和g可以看做要实现的功能的容器。
看到这里可能有人会想说,那这样的话是不是只能组合两个呐,万一要组合更多,是不是compose要把内部重写了。当然不是,我们可以写作这样compose(f,compose(g, h)),还能这样compose(compose(f, g), h),这个时候你会惊奇的发现,这居然是等效的。这里可以想到什么?这不就是数学里面的结合律么?既然是结合律,那就还能这么写,compose(f, g, h)!按理来说应该是正确的,不过由于这语言底层还是基于命令式的编程的,所以这里的需要换一种方式来实现compose。
…先参考一下ramda或者lodash吧。下次再更新啦…
对了,要说到用途嘛?给你们看一个koa巧妙加载中间件的例子,也就是我这次用在IoT项目中的小示例:

const app = new Koa() const MIDDLEWARE = ['bodyparser', 'logs', 'router', 'webpack-dev'] const useMiddleware = app => { R.map( R.compose( R.forEachObjIndexed( initWith => initWith(app) ), require, name => resolve(__dirname, `./middlewares/${name}`) ) )(MIDDLEWARE) } useMiddleware(app)

这样做的话,中间件加载直接只需在数组中添加就完成了。简洁,直观,大方,无敌!
好啦,下回见!

留言

  • 暂时没有留言,来留下你的留言吧!

评论

看不清?换一个