函数式编程

1.什么是函数式编程

函数式编程是一种思维方式,强调在编程过程中把更多的关注点放在如何去构建映射关系。

函数式编程和命令式编程区别:函数式编程关心数据的映射,命令式编程关心解决问题的步骤。

下面举个例子展示下两者区别:

假设我们需要进行如下结构转换:

 ['john-rose', ‘linda-luo ', ‘lucy-han']   
 // 转成 
 [{name: 'John Rose'}, {name: Linda Luo'}, {name: Lucy Han'}]

命令式编程

思路:

/*  1.定义一个临时变量 newArr。

  2.做一个循环,需要做 arr.length 次。

  3.循环每次把名字的首位取出来大写,然后拼接剩下的部分。

  ……

  4.最后返回结果。*/

//代码实现
const arr = ['john-rose', 'linda-luo', 'lucy-han'];
const newArr = [];
for (let i = 0, len = arr.length; i < len ; i++) {
  let name = arr[i];
  let names = name.split('-');
  let newName = [];
  for (let j = 0, naemLen = names.length; j < naemLen; j++) {
    let nameItem = names[j][0].toUpperCase() + names[j].slice(1);
    newName.push(nameItem);
  }
  newArr.push({ name : newName.join(' ') });
}
return newArr;

缺点:产生一堆中间临时变量,同时过程中掺杂了大量逻辑,可读性差、不易维护。通常一个函数需要从头读到尾才知道它具体做了什么,而且一旦出问题很难定位。

函数式编程

思路:

/*
1.将string数组转化为对象数组    [a, b, c]  ->  [{name: a’}, {name: b’}, {name: c’}]

2.将单个string转化为单个object( convert2Obj )   String -> Object    a -> {name: a’}

    a.将string转为为指定的string( capitalizeName )  String -> String    a –> a’

    b.将string转化为object( genObj ) String -> Object  a' -> {name: a’}
*/
// 代码实现
import * as R from 'ramda'
const {curry, compose, join, map, split} = R
const capitalize = x => x[0].toUpperCase() + x.slice(1).toLowerCase();

const genObj = curry((key, x) => {
  let obj = {};
  obj[key] = x;
  return obj;
}) 

const capitalizeName = compose(join(' '), map(capitalize), split('-'));
const convert2Obj = compose(genObj('name'), capitalizeName)
const convertName = map(convert2Obj);

convertName(['john-rose', 'linda-luo', 'lucy-han'])

特点: 着眼点是函数,而不是过程。强调的是如何通过函数的组合变换去解决问题

2. 函数式编程的特点

  • 函数是“一等公民”

这是函数式编程得以实现的前提,因为我们基本的操作都是在操作函数。这个特性意味着函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。

  • 声明式编程

函数式编程大多时候都是在声明我需要做什么,而非怎么去做。这种编程风格称为声明式编程 。这样有个好处是代码的可读性特别高,同时也方便我们进行分工协作。

  • 函数惰性执行

所谓惰性执行指的是函数只在需要的时候执行,即不产生无意义的中间变量。函数式编程跟命令式编程最大的区别就在于几乎没有中间变量,它从头到尾都在写函数。

  • 无状态和数据不可变

数据不可变: 它要求你所有的数据都是不可变的,这意味着如果你想修改一个对象,那你应该创建一个新的对象用来修改,而不是修改已有的对象。

无状态: 主要是强调对于一个函数,不管你何时运行,它都应该像第一次运行一样,给定相同的输入,给出相同的输出,完全不依赖外部状态的变化。

  • 没有副作用

副作用指:在完成函数主要功能之外完成的其他副要功能。在我们函数中最主要的功能当然是根据输入返回结果,而在函数中我们最常见的副作用就是随意操纵外部变量, 如修改全局变量,修改入参等。

  • 纯函数

不依赖外部状态(无状态): 函数的的运行结果不依赖全局变量,this 指针,IO 操作等。

没有副作用(数据不变): 不修改全局变量,不修改入参。

所以纯函数才是真正意义上的 “函数”, 它意味着相同的输入,永远会得到相同的输出

纯函数的意义:

a. 便于测试和优化 b. 可缓存性 c. 更少的 Bug

3.流水线的构建(柯里化、函数组合)

函数式编程更为关注构建关系,数据可以不断的从一个函数的输出可以流入另一个函数输入,最后再输出结果。我们可以把上面函数式编程的过程抽象为流水线的构建,有两种操作是必不可少的那无疑就是柯里化(Currying)函数组合(Compose),柯里化其实就是流水线上的加工站,函数组合就是我们的流水线,它由多个加工站组成。

流水线1.png
流水线2.png

接下来,就让我们看看柯里化(Currying)函数组合(Compose)

加工站-函数柯里化

柯里化的意思是将一个多元函数,转换成一个依次调用的单元函数

f(a,b,c) → f(a)(b)(c)

为什么这个单元函数很重要?因为函数的返回值,有且只有一个, 如果我们想顺利的组装流水线,那我就必须保证我每个加工站的输出刚好能流向下个工作站的输入。因此,在流水线上的加工站必须都是单元函数。

  • 部分函数应用 vs 柯里化
// 柯里化 
 f(a,b,c) → f(a)(b)(c) 

// 部分函数调用
f(a,b,c) → f(a)(b,c) 、 f(a,b)(c)

部分函数( Partial Function Application )应用强调的是固定一定的参数,返回一个更小元的函数

柯里化强调的是生成单元函数部分函数应用的强调的固定任意元参数,而我们平时生活中常用的其实是部分函数应用,这样的好处是可以固定参数,降低函数通用性,提高函数的适合用性。

  • 高级柯里化

现成的函数库(如Ramda)提供的curry 函数,做了优化,不是纯粹的柯里化,可理解成高级柯里化。

实现可以根据你输入的参数个数,返回一个柯里化函数/结果值。即,如果你给的参数个数满足了函数条件,则返回值

我们可以用高级柯里化去实现部分函数应用,但是柯里化不等于部分函数应用

//代码演示
//简易实现
const curry = function(func) {
  return function curried(...args) {
    if (args.length < func.length) {
      return function(...args2) {
        return curried.apply(this, args.concat(args2));
      }
    }
    return func.apply(this, args);
  }
}

const add = R.curry((x, y, z) => x + y + z); 

const add7 = add(7); 
add7(1,2) // 10 

const add1_2 = add(1,2); 
add1_2(7) // 10 

add(7)(1)(2) // 10

流水线-函数组合

函数组合的目的是将多个函数组合成一个函数。下面来看一个简化版的实现:

const compose = (...fns) => 
    (...args) => 
            fns.reduceRight((val, fn) => fn.apply(null, [].concat(val)), args); 

const f = x => x + 1;
const g = x => x * 2; 
const t = (x, y) => x + y; 

let fgt = compose(f, g, t); 

fgt(1, 2); // 3 -> 6 -> 7

函数组合的好处

函数组合的好处显而易见,它让代码变得简单而富有可读性,同时通过不同的组合方式,我们可以轻易组合出其他常用函数,让我们的代码更具表现力。

大型的程序,都可以通过一步步的拆分组合去实现,而剩下要做的,就是去构造足够多的积木块(函数)。

4.总结

优点:

  • 代码简洁,开发快速:

函数式编程大量使用函数的组合,函数的复用率很高,减少了代码的重复,因此程序比较短,开发速度较快。

  • 接近自然语言,易于理解:

函数式编程大量使用声明式代码,基本都是接近自然语言的,加上它没有乱七八糟的循环,判断的嵌套,因此特别易于理解。

  • 易于并发编程:

函数式编程没有副作用,所以函数式编程不需要考虑“死锁”(Deadlock),所以根本不存在“锁”线程的问题。

  • 更少的出错概率:

因为每个函数都很小,而且相同输入永远可以得到相同的输出,因此测试很简单,同时函数式编程强调使用纯函数,没有副作用,因此也很少出现奇怪的 Bug。

缺点:

  • 性能:

函数式编程往往会对一个方法进行过度包装,从而产生上下文切换的性能开销。

  • 资源占用:

在 JS 中为了实现对象状态的不可变,往往会创建新的对象,因此,它对垃圾回收所产生的压力远远超过其他编程方式。这在某些场合会产生十分严重的问题。

  • 递归陷阱:

在函数式编程中,为了实现迭代,通常会采用递归操作。

总结:

因此,在性能要求很严格的场合,函数式编程其实并不是太合适的选择。

我们完全可以在日常工作中将函数式编程作为一种辅助手段,在条件允许的前提下,借鉴函数式编程中的思路,例如:多使用纯函数减少副作用的影响;使用柯里化增加函数适用率;使用它的编程风格,减少无意义的中间变量,让代码更具可读性。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 193,968评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,682评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,254评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,074评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,964评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,055评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,484评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,170评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,433评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,512评论 2 308
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,296评论 1 325
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,184评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,545评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,150评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,437评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,630评论 2 335

推荐阅读更多精彩内容