JavaScript 函数式编程基础

发布于 2024-01-20 07:53:04 字数 6812 浏览 39 评论 0

纯函数

对于相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,也不依赖外部环境的状态。

无副作用,对于固定的输入,有固定的输出

const arr = [1,2,3,4,5];
 
// Array.slice 无副作用
arr.slice(0,3);
//=> [1,2,3]
arr.slice(0,3);
//=> [1,2,3]

// Array.splice 有副作用
arr.splice(0,3);
//=> [1,2,3]
arr.splice(0,3);
//=> [4,5]
arr.splice(0,3);
//=> []

不依赖外部环境

// 不纯,该函数的行为需要由外部环境决定
const CONST_AGE = 18;
const checkage = age => age > CONST_AGE;
 
// 纯,硬编码,扩展性较差,可通过 curry 来解决
const checkage = age => age > 18;

柯里化

传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。

预加载函数,是一种对参数的缓存

// 柯里化两个纯函数
const match = curry((reg, str) => str.match(reg));
const filter = curry((func, arr) => arr.filter(func));

// 判断字符串里有没有空格
const haveSpace = match(/\s+/g);

haveSpace("ffffffff");
//=> null

haveSpace("a b");
//=> [" "]

// 过滤出有空格的字符串
const haveSpaceFilter = filter(haveSpace);

haveSpaceFilter(["abcdefg", "Hello World"]);
//=> ["Hello world"]

实现 curry 函数

function curry(func) {
    const funcArgsLen = func.length;

    return function curryWrap(...args) {
        if(args.length >= funcArgsLen) {
            return func.apply(null, args);
        } else {
            return function(...argsWrap) {
                return curryWrap.apply(null, args.concat(argsWrap));
            };
        }
    };
}

组合

解决形如 f(g(h(x))) 的函数嵌套问题。

将多个函数组合起来,除了第一个参数,其他函数接受上一函数的返回值,初始函数参数为 多元 ,其余函数参数为 一元

function add(a, b) {
    return a + b;
}

function square(n) {
    return n * n;
}

const addSquare = compose(square, add);

addSquare(1, 2);
//=> 9

实现 compose 函数

function compose(...funcs) {
    const length = funcs ? funcs.length : 0;

    return function(...args) {
        let index = 0;
        let result = length ? funcs[length - 1].apply(this, args) : args[0];

        while (++index < length) {
            result = funcs[length - 1 - index].call(this, result);
        }

        return result;
    };
}

Functor

Functor(函子) 是实现了 map 并遵守一些特定规则的容器类型。容器是一个对于函数调用的抽象,我们赋予容器自己去调用函数的能力,这样容器就可以自由地选择何时何地如何操作函数,以致于其拥有惰性求值、错误处理、异步调用等非常厉害的特性。

// 实现一个简单的容器
function Container(x) {
    this.__value = x;
}

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

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

console.log(Container.of(1).map(x => x + 1).map(x => `Result is ${x}`));

Maybe

// 为 map 函数添加一个检查空值的特性,我们称之为 Maybe
function Maybe(x) {
    this.__value = x;
}

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

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

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

const curriedAdd = curry((a, b) => a + b);
const addTen = curriedAdd(10);

const curriedProp = curry((propName, obj) => obj[propName]);
const getAgeProp = curriedProp('age');

Maybe.of({ name: 'Rose' }).map(getAgeProp).map(addTen)
//=> Maybe { __value: null }

Maybe.of({ name: 'Rose', age: 10 }).map(getAgeProp).map(addTen)
//=> Maybe { __value: 20 }

Either,不只是用来做错误处理

const Left = function(x) {
  this.__value = x;
}

const Right = function(x) {
  this.__value = x;
}

// same
Left.of = function(x) {
  return new Left(x);
}

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

// different!
Left.prototype.map = function(func) {
  return this;
}

Right.prototype.map = function(func) {
  return Right.of(func(this.__value));
}

const getAge = user => user.age ? Right.of(user.age) : Left.of('ERROR!');

console.log(getAge({ name: 'Rose', age: 10 }).map(age => `Age is ${age}`));
//=> Right { __value: 'Age is 10' }

console.log(getAge({ name: 'Rose' }).map(age => `Age is ${age}`))
//=> Left { __value: 'ERROR!' }

IO

// IO 包含的是被包裹的操作的返回值,�可以将不纯的操作(如 IO�,网络请求,DOM)包裹到一个函数内,
// 延迟操作的执行,当被调用时才开始求值,这个特性应用我们称之为惰性求值。

// 示例:从当前 url 解析参数
function IO(func) {
    this.__value = func;
}

IO.of = x => new IO(_ => x);

IO.prototype.map = function(func) {
    return new IO(compose(func, this.__value));
};

// 一些基础函数
const split = curry((char, str) => str.split(char));
const first = arr => arr[0];
const last = arr => arr[arr.length - 1];
const filter = curry((func, arr) => arr.filter(func));

// x 类型为 Array 或 Functor
const map = curry((func, x) => x.map(func));
const eq = curry((x, y) => x === y);

const toPairs = compose(map(split('=')), split('&'));
// toPairs('a=1&b=2')
//=> [['a', '1'], ['b', '2']]

const params = compose(toPairs, last, split('?'));
// params('http://xxx.com?a=1&b=2') 
//=> [['a', '1'], ['b', '2']]

const getParam = url => key => map(compose(Maybe.of, filter(compose(eq(key), first)), params))(url);

// const url = new IO(_ => window.location.href);
const url = new IO(_ => 'http://xxx.com?a=1&b=2'); 

const findParam = getParam(url);

// 上述代码都是纯函数,以下求值的过程是非纯的
console.log(findParam('a').__value());
//=> Maybe { __value: [ [ 'a', '1' ] ] }

Monad,用它来实现一个 Promise

首先来实现一个 Linux 命令行下的 cat 命令。

const fs = require('fs');

const readFile = filename => new IO(_ => fs.readFileSync(filename, 'utf-8'));
const print = x => new IO(_ => {
    console.log(x);
    return x;
});

const cat = compose(map(print), readFile);

cat('./file.js');
//=> IO { __value: IO { __value: _ => 'file\'s content.' } }

这里涉及两个 IO,所以我们得到了两层 IO,想要运行它的话,只能调用 cat('./file.js').__value().__value() 。我们可以通过实现一个 join 方法,来剥开每一层的 Functor,把里面的东西暴露给我们。

const join = x => x.join();

IO.prototype.join = function() {
    return this.__value ? this.__value() : IO.of(null);
};

const cat = compose(join, map(print), readFile);

cat('./file.js').__value();

join 方法可以把 Functor 拍平(flatten),我们一般把具有这种能力的 Functor 称之为 Monad 。我们不可能总是在 map 之后手动调用 join 来剥离多余的包装,否则代码会长得像这样,

const doSomething = compose(join, map(f), join, map(g), join, map(h));

所以我们需要一个叫 chain 的方法来实现我们期望的链式调用,它会在调用 map 之后自动调用 join 来去除多余的包装,

const chain = _.curry((func, functor) => functor.chain(func));

IO.prototype.chain = function(func) {
  return this.map(func).join();
}

// Promise Like
someMonad.chain(f).chain(g).chain(h)

前端应用

  • RxJS
  • cycleJS
  • lodashJS
  • underscoreJS

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

隔纱相望

暂无简介

文章
评论
27 人气
更多

推荐作者

櫻之舞

文章 0 评论 0

弥枳

文章 0 评论 0

m2429

文章 0 评论 0

野却迷人

文章 0 评论 0

我怀念的。

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文