JavaScript 函数式真正的浅析

0x00 入门的导语(空话)

近来两年你要说函数式编程不火的话, 那是不可能的, 是人都晓得函数式编程很火.为何函数式编程会火呢, 在于它的头脑, 很壮大, 很强势!尤其是前端的redux更是在reducer上完整运用纯函数, 函数式的优点逐渐被发掘出来, 笔者近来看了一些函数式方面的东东, 如今发出来给人人进修进修, 趁便我也进修进修怎样写文章… :P

经常使用的函数式库:

  • ramda 设想很棒的一个库

  • lodash 比较经常使用的一个库

  • underscore 应当也不错的一个库

0x01 纯函数

定义: 雷同输入一定获得雷同输出且运转历程当中不修正,不读取外部环境的变量的函数

说出来一定不好明白, 照样要看看代码. 就彷佛你不看国足竞赛永久不晓得国足为何会输给月薪几百块的叙利亚.

// Array.slice 关于牢固输入一定是牢固输出, 且不依靠外部变量, 啥? 依靠了arr变量吗?
// 实在这类写法和Array.prototype.slice(arr, 0, 3); 是一样的. 如许就明白了,
// 你还学到一个东西 Array.slice是不会修正原数组滴!
var arr = [1,2,3,4,5];
arr.slice(0,3);

 // Array.splice 会修正xs, 所以是不纯的, 所以雷同的输入不会有雷同的输出!
var xs.splice(0,3);
//=> [1,2,3]
xs.splice(0,3);
//=> [4,5]
xs.splice(0,3);
//=> []

纯函数的优点: 不会去修正外部变量就不会发生线程安全问题.可以极大的削减体系庞杂水平

0x02 函数的柯里化

看! 代码!

// 挪用 doWht('我', '家里', '饭');
let doWhat = (who, where, what) => {
  return who + '在' + where + '做' + what
}

// 柯里化后的等价效果
// 挪用 doWhat('我')('家里')('饭')
let doWhat = who => where => what => {
  return who + '在' + where + '做' + what
}

// 假定如今晓得是'我'在'家', 至于做什么是不晓得的
// tmp函数就已帮我们保留了值, 如许是非常天真的.
let doWhatCurry = doWhat('我')('家里')

上面提到的库里都有一个叫curry的函数会将一个一般的函数柯里化.

0x03 函数的组合

函数组合是将函数组合在一起, 天生一个新的函数


// h(g(f(x))) 这是之前挪用函数的体式格局
var add1 = x => x + 1
var mul5 = x => x * 5
// compose会天生一个新的函数, 吸收的参数悉数传给add1, 然后add1的返回值传给mul5(注重注重!, mul5的参数个数只能有一个!!!), 然后compose天生的新的函数的返回值就是mul5的返回值.
compose(mul5, add1)(2)

函数组合非常壮大, 可以经由历程组合的体式格局来天生新的函数, 这是非常爽的. 假如你运用天真, 会极大的削减你的代码量(假如不能削减别喷我啊), compose的实如今上面提到的三个库中都有完成.

0x04 声明式与敕令式作风

敕令式的作风让我们经由历程代码指导机械, 让机械一步一步完成我们要的使命; 而声明式则是直接通知机械我要做啥, 更直观.

//敕令式
var persons = [...]
for (var i = 0; persons.length; ++i) {
  persons[i] = persons[i].toUppercase()
}

//声明式
var persons = [...]
persons.map(person => person.toUppercase())

0x05 Point free作风

// 假定假如 
let map = fn => list => list.map(fn);
let add = (a, b) => a + b;

// 函数incrementAll不是point free 作风
// 由于这里提到了numbers参数, 须要给出一个定名.
// 如许定义函数会致使我们须要多定名一个变量. 贫苦!
let incrementAll = (numbers) => map(add(1))(numbers);

// Point free作风的定义要领
// 假定add被柯里化过了
let incrementAll = map(add(1))

如今是引荐运用point free作风的代码(定义函数时), 这会削减我们不必要的定名. 多用这类作风哦!

0x06 容器(Functor)

容器代表了一个值, 一个恣意值. 他就彷佛是函数式编程里的变量,函数的一个铠甲.可以让你的变量,函数在工程的疆场中所向无敌!


var Container = function(x) {
  this.__value = x;
}

Container.of = x => new Container(x);

Container.prototype.map = function(f){
  return Container.of(f(this.__value))
}

Container.of(3).map(x => x+1).map(x => x*5)
// of用来构建容器, map用来变更容器
// Functor可以做许多许多事变, 详细的? 往下引见.
// Maybe就是在一般容器上新增了一个搜检空值的行动. 
var Maybe = function(x) {
  this.__value = x;
}

Maybe.of = function(x) {
  return new Maybe(x);
}

Maybe.prototype.map = function(f) {
  return this.isNothing() ? Maybe.of(null) : Maybe.of(f(this.__value));
}

Maybe.prototype.isNothing = function() {
  return (this.__value === null || this.__value === undefined);
}

// 例子, 假如name是空的话就会输出空了
var functor = Maybe.of({name: ‘mrcode'})
functor
    .map(value => value.age)
    .map(String.prototype.upperCase)
    .map(value => console.log(value))

这个Maybe到底有啥用呢? 就是空值检测, 看上面的例子, 假如不举行判空的话, 第二个map就会挪用String.prototype.upperCase函数, 会抛出非常的, 怕了吧? :P, 而且, 如今许多言语,swift等都添加了相似的支撑. optional

Maybe只能判空, 然则Either才是真正的处置惩罚缺点的容器, Either有两个子类, Left和Right.

// Promise是经由历程catch要领来吸收缺点的 如:
doSomething()
    .then(async1)
    .then(async2)
    .catch(e => console.log(e));

// 完整一样    
var Left = function(x) {
  this.__value = x;
}
var Right = function(x) {
  this.__value = x;
}

// 完整一样
Left.of = function(x) {
  return new Left(x);
}
Right.of = function(x) {
  return new Right(x);
}

// 这里差别!!!
Left.prototype.map = function(f) {
  return this;
}
Right.prototype.map = function(f) {
  return Right.of(f(this.__value));
}

// 运用:
var getAge = user => user.age ? Right.of(user.age) : Left.of("ERROR!")
getAge({name: 'stark', age: '21'}).map(age => 'Age is ' + age);
//=> Right('Age is 21')

getAge({name: 'stark'}).map(age => 'Age is ' + age);
//=> Left('ERROR!')

Left会跳过一切实行历程, 直达效果, 这就彷佛Right是流程图里一个又一个指向下一个使命的箭头, 而Left直接指向了效果, 是缺点的效果.

0x07 IO

诶, 函数式编程里, 涉及到IO老是让人为难的, 蓝瘦的很..幸亏, 有一种叫做IO的东西特地处置惩罚IO这类东西(别嫌绕哈), 看代码,

// 没缺点
var IO = function(f) {
    this.__value = f;
}

// ??? 看不懂, 待会诠释..
IO.of = x => new IO(_ => x);

// ??? 这是啥子鬼????
IO.prototype.map = function(f) {
    return new IO(compose(f, this.__value))
};

威望解答: 这里的IO里存的是一个函数, 包裹了外部环境变量的函数, 我们传入了一个函数, 这个函数里包括了现实的值,会举行IO操纵. 我们把不纯的IO操纵放到了这个函数里, 总体上看, 我们的IO对象, 是不会实行这些不纯的操纵的. 它依然是纯的, 由于IO操纵压根就没实行内部包括的函数, 这个函数是外部挪用者去实行的. 也就是说, 不纯的操纵是外部的人干的, 和我们的IO对象一丢丢关联都木有!(干得美丽!) 看一个例子.

var io_document = new IO(_ => window.document);
io_document.map(function(doc){ return doc.title });
// 获得IO(documen.title)

科普: 这里你没有获得document.title, 你获得的仅仅是一个会返回document.title的一个函数, 这个函数是不纯的, 然则实行不是由上面的代码实行的, 锅在挪用函数的人身上! 上面的代码依然是’纯’的!

0x08 Monad

看这个部份的时刻发起看一下IO的完成, 好好明白一下, 我晓得有点烧脑, 然则看一下没害处!玩过Promise的都晓得, Promise.then传进去的函数可以返回一个新的Promise. Promise就是Monad.

0x09 函数式编程的运用

react中的纯组件

// 牢固的输入获得牢固的输出 纯组件极大的增加了react的天真水平
// app 的状况交给一些状况机治理 比方redux
var Text = props => (
    <div style={props.style}>{props.text}</div>
)

redux中的reducer

// 输入当前状况和action, 输出nowState
reducer(currentState, action) => newState

0x10 总结一下

确实是如许, 不总结的话就不像是一篇文章了, 照样总结下吧:

  • 纯函数的观点以及函数柯里化和函数的组合

  • 容器观点, Container和Maybe, Either的派生Left,Right, IO作用.

  • 函数式编程的运用

参考文章

JavaScript函数式编程3

好了, 再会 古德曼~

    原文作者:mrcode
    原文地址: https://segmentfault.com/a/1190000007180426
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞