函数式编程

31 阅读5分钟

函数式编程

JavaScript 函数式编程是指使用函数来进行编程的一种范式。在函数式编程中,函数被视为一等公民,可以作为变量、参数和返回值来使用。主要是利用函数把计算过程封装起来,通过组合各种函数来计算结果。

函数式编程强调的是"做什么"而不是"如何做"。这是通过使用一系列的函数来描述计算过程,而不是通过一系列的命令来改变状态。在函数式编程中,函数的输出完全由输入决定,不依赖于或改变程序的状态。这种特性使得函数式编程的代码更加可预测和易于理解。

面向对象编程(OOP)和函数式编程(FP)是当前最主流的两种编程范式。面向对象编程以对象作为基本单位,强调对象之间的交互和状态变化。而函数式编程以函数为基本单位,强调的是数据的映射关系,尽可能地避免状态变化。

函数式编程的特点:

函数是一等公民:在函数式编程中,函数被视为一等公民,可以作为变量、参数和返回值来使用。

声明式编程:通过写表达式的方式来声明我们想干什么,而不是通过命令式编程去一步一步的指示应该怎么做。

纯函数:没有副作用的函数。相同的输入有相同的输出。

引用透明:一个函数只会用到传递给它的变量以及自己内部创建的变量,不会使用到其他变量。

不可变数据:函数式编程强调使用不可变的数据结构。这种数据结构在被创建之后不能被修改,只能通过复制来创建新的数据结构。使用不可变数据可以提高代码的简洁性、健壮性和安全性。

惰性执行:函数只在需要的时候执行,不产生无意义的中间变量。

函数式编程模型:

闭包

纯函数

高阶函数:函数以函数为参数,或以函数为返回值,或者既以函数为参数又以函数为返回值 (map filter reduce)

函数柯理化

函数组合:函数组合可以让我们把多个函数组合成一个新的函数,然后在执行的过程中,我们可以把参数输入给第一个函数,当它执行完成以后会返回一个中间结果。并且把这个中间结果交接下一个函数去处理,当最后一个函数执行完毕之后,我们会把最终结果返回。只关注函数结果,不关注函数内部是怎么实现的。

// 这就是函数组合
const compose = (f, g) => x => f(g(x))

const compose = function(f,g) {
  return function(x) {
    return f(g(x));
  };
};

const f = x => x + 1;
const g = x => x * 2;
const fg = compose(f, g);
fg(1) //3

实际案例

var toUpperCase = function(str) { return str.toUpperCase(); };
var exclaim = function(str) { return str + '!'; };
var shout = compose(exclaim, toUpperCase);
 
shout('hello world');
// HELLO WORLD!


var arr = [1, 2, [2, 10, 0, [5, 6, 4, [7, 1]]]];
var flatten = function (arr) {
    while(arr.some(Array.isArray)){
        arr = [].concat(...arr)
    }
    return arr;
};
var unique = function (arr) { return Array.from(new Set(arr)); };
var flattenAndUnique = compose(unique, flatten);
var result = flattenAndUnique(arr) // [1, 2, 10, 0, 5, 6, 4, 7]

// 此时,新增一个需求,需要排序
// ...
// 新增一个数组排序方法
var sort = function (arr) { return arr.sort(function(a, b) {
    return  a - b;
}) };
var flattenAndUniqueAndSort = compose(sort, unique, flatten);
var result = flattenAndUniqueAndSort(arr) //  [0, 1, 2, 4, 5, 6, 7, 10]

以上代码要符合开闭原则:开闭原则是指一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。

函数组合满足结合律

符合结合律意味着不管你是把 g 和 h 分到一组,还是把 f 和 g 分到一组都不重要

compose(toUpperCase, compose(head, reverse));
 
// 或者
compose(compose(toUpperCase, head), reverse);

实现compose(所有传入compose的函数从右往左执行)

// reduceRight实现
const compose = (...fns) => (value) => fns.reduceRight((acc, fn) => fn(acc), value)

实现管道pipe(所有传入pipe的函数从左往右执行)

const pipe = (...fns) => (value) => fns.reduce((acc, fn) => fn(acc), value)

compose传入的函数只能有一个形参,但是往往某些函数需要多个形参,这个时候就需要函数柯理化了

function currying(fn, ...args) {
    if (args.length >= fn.length) {
        return fn(...args)
    }
    return function (...args2) {
        return currying(fn, ...args, ...args2)
    }
}

// 例子
function stringToUpper(str) {
    return str.toUpperCase()
}

function stringReverse(str) {
    return str.split('').reverse().join('')
}

function find(str, targetStr) {
    return str.includes(targetStr)
}

function judge(is) {
    console.log(is ? 'yes' : 'no')
}

// 柯里化 find 函数
function findCurry(targetStr) {
    return str => str.includes(targetStr)
}

const findTaoweng = findCurry('TAOWENG')

const result = compose(judge, findTaoweng, stringReverse, stringToUpper)

部分应用函数

把一个函数先传入一部分参数,返回一个更少参数的函数,调用时再传入剩下的参数(它和柯理化还是有区别的,柯理化是连续调用,可会调用很多次,是生成嵌套函数,并且柯理化每次返回的函数的形参个数是不固定的。)

// 通用的部分应用函数的核心实现
function partial(fn, ...args) {
    return (..._arg) => {
        return fn(...args, ..._arg);
    }
}

// 通用的部分应用函数的核心实现
function partial(fn, ...args) {
    return fn.bind(null, ...args)
}

一句话总结函数式编程:利用函数组合compose,把一系列的函数抽离出来,只关注函数结果,不关注函数怎么实现,通过compose的计算最终得到结果。