Skip to content

纯函数、函数式编程、柯里化 #136

@TieMuZhen

Description

@TieMuZhen

一、纯函数

前言

纯函数是函数式编程的基础

纯函数的概念

首先我们来看看纯函数的基本概念:

相同的输入,总是会的到相同的输出,并且在执行过程中没有任何副作用。

该怎么去理解上面的概念呢?我们要把上面这句话拆成两部分来看。

1、相同的输入,总是会得到相同的输出。
来看看下面的例子:

let a = 1;

function xAdd(x) {
    return x + a;
};
xAdd(1); //2

上面这个函数就不是一个纯函数,因为在我们程序执行的过程中,变量a很可能会发生改变,当变量a发生改变时,我们同样执行xAdd(1)时得到的输出也就不同了。

再看另一个例子:

function sum(x, y) {
    return x + y;
};
sum(1,2); //3

在这个例子中,符合相同的输入得到相同的输出这个概念,sum是一个纯函数。

2、执行过程中没有任何副作用

这里我们要搞清楚什么是副作用,这里的副作用指的是函数在执行过程中产生了外部可观察变化。

  • 发起HTTP请求
  • 操作DOM
  • 修改外部数据
  • console.log()打印数据
  • 调用Date.now()或者Math.random()

上面一系列操作都可以被称为是副作用。下面可以接着看一个修改外部数据从而产生副作用的例子:

let a = 1;
function func() {
    a = 'b';
};
func();
console.log(a); // b

我们运行了func函数,外部的变量a的值发生了改变,这就是产生了所谓的副作用,所以func不是一个纯函数。当我们这样进行修改:

function func2() {
    let a = 1;
    a = 'a';
    return a
};
func(); // a

函数fun2不会对产生外部可观察变化,也就不会产生副作用,它就是一个纯函数。

一个纯函数,上面所说的两个条件缺一不可。

纯函数的好处

通过了解纯函数的概念,我相信有的小伙伴已经能感觉到纯函数的一些的好处了:

  • 更容易进行测试,结果只依赖输入,测试时可以确保输出稳定
  • 更容易维护和重构,我们可以写出质量更高的代码
  • 更容易调用,我们不用担心函数会有什么副作用
  • 结果可以缓存,因为相同的输入总是会得到相同的输出

纯函数运用的经典案例

Lodash 是一个一致性、模块化、高性能的 JavaScript 实用工具库。我相信很多小伙伴也经常用到吧,这也是纯函数代表。

合理运用纯函数编写公共方法

假设我们要编写一个把数组中的小写字母转为大写字母的公共方法:

let upperCaseLists = (value) => {
    return value.map((item) => item.toUpperCase())
}

纯函数组件的缺点

没有生命周期。

二、函数式编程

函数式编程好处

1. 代码简洁,开发快速
函数式编程大量使用函数,减少了代码的重复,因此程序比较短,开发速度较快。

2. 接近自然语言,易于理解
函数式编程的自由度很高,可以写出很接近自然语言的代码。

前文曾经将表达式(1 + 2) * 3 - 4,写成函数式语言:

subtract(multiply(add(1,2), 3), 4)

对它进行变形,不难得到另一种写法:

add(1,2).multiply(3).subtract(4)

这基本就是自然语言的表达了。再看下面的代码,大家应该一眼就能明白它的意思吧:

merge([1,2],[3,4]).sort().search("2")

因此,函数式编程的代码更容易理解。

3. 更方便的代码管理
函数式编程不依赖、也不会改变外界的状态,只要给定输入参数,返回的结果必定相同。因此,每一个函数都可以被看做独立单元,很有利于进行单元测试(unit testing)和除错(debugging),以及模块化组合。

4. 易于"并发编程"
函数式编程不需要考虑"死锁"(deadlock),因为它不修改变量,所以根本不存在"锁"线程的问题。不必担心一个线程的数据,被另一个线程修改,所以可以很放心地把工作分摊到多个线程,部署"并发编程"(concurrency)。

5. 代码的热升级
函数式编程没有副作用,只要保证接口不变,内部实现是外部无关的。所以,可以在运行状态下直接升级代码,不需要重启,也不需要停机。

函数式编程缺点

  • 性能:函数式编程相对于指令式编程,性能绝对是一个短板,因为它往往会对一个方法进行过度包装,从而产生上下文切换的性能开销
  • 递归陷阱:在函数式编程中,为了实现迭代,通常会采用递归操作

三、柯里化

柯里化是把一个多参数函数转化成一个嵌套的一元函数的过程
一个二元函数如下:

let fn = (x,y)=>x+y;

转化成柯里化函数如下:

const curry = function(fn){
    return function(x){
        return function(y){
            return fn(x,y);
        }
    }
}
let myfn = curry(fn);
console.log( myfn(1)(2) );

上面的curry函数只能处理二元情况,下面再来实现一个实现多参数的情况

// 多参数柯里化;
const curry = function(fn){
    return function curriedFn(...args){
        if(args.length<fn.length){
            return function(){
                return curriedFn(...args.concat([...arguments]));
            }
        }
        return fn(...args);
    }
}
const fn = (x,y,z,a)=>x+y+z+a;
const myfn = curry(fn);
console.log(myfn(1)(2)(3)(1));

关于柯里化函数的意义如下:

  • 让纯函数更纯,每次接受一个参数,松散解耦
  • 惰性执行

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions