函数式编程(学习笔记)

本文深入探讨了函数式编程的概念,包括它与面向对象编程的区别、函数是一等公民的特性、纯函数的优势以及高阶函数、柯里化、组合函数等核心概念。同时,介绍了函子、Maybe函子、Either函子和IO函子等抽象概念,并展示了如何在JavaScript中实现和应用这些概念。最后,讨论了函数式编程在实际开发中的灵活性和实用性。
摘要由CSDN通过智能技术生成

为了方便自己学习以及理解,把自己的学习笔记放在网上,可以在上班或在家忘记的时候拿出来看看(仅供自己参考)

函数式编程

  • 面向对象:就是将现实世界中的事物抽象成程序世界中的类和对象,通过封装,继承和多态演示事物事件的联系。
  • 函数式编程是一种思想,是一种编程规范。就是将现实世界中的事物和事物之间的联系抽象到程序世界。函数式编程说的是一种映射关系,而不是程序中的函数(方法)。

函数是一等公民

  • 函数是变量
  • 函数作为参数
  • 函数作为返回值

高阶函数

  • 只需要关注与我们的目标, 使代码更简洁

纯函数

  • 相同的输入永远会得到相同的输出, 没有副作用

好处

  • 可以缓存,提高性能
  • 纯函数让测试更方便
  • 并行处理, 纯函数不需要访问共享的内存数据,所以在并行环境下可以任意运行纯函数(Web Worker)

柯里化

  • 当一个函数有多个参数的时候先传递一部分参数调用它(这部分参数以后永远不变)

  • 返回一个新的函数接收剩余的参数,返回结果

  • 总结:

    1.可以让我们给一个函数传递较少的参数得到一个已经记住了某些固定参数的新函数
    2.这是一种对函数参数的"缓存"
    3.让函数变得更灵活,让函数的粒度更小
    4.可以把多元函数换成一元函数,可以组合使用函数产生强大的功能

    function curry(fn, args) {
        const len = fn.length;
        args = args || [];
        return function() {
            const subArgs = args.slice();
            for (let i = 0; i < arguments.length; i++) {
                subArgs.push(arguments[i]);
            };
            if (subArgs.length >= len) {
                return fn.apply(this, subArgs);
            } else {
                return curry.call(this, fn, subArgs);
            };
        };
    };

组合函数

  • 如果一个函数要经过多个函数处理才能得到最终值,这个时候可以把中间过程的函数合并成一个
  • 函数组合默认是从右到左执行
    function compose(...args) {
        args = args.reverse();
        return function(value) {
            return args.reduce((res, fn) => fn(res), value);
        };
    };

Point Free

定义:可以把数据处理的过程定义成与数据无关的合成运算,不需要用到代表数据的那个参数,只要把简单的运算步骤合成到一起,在这种模式之前我们需要定义一些辅助的基本运算函数

  • 不需要指明处理的数据
  • 只需要合成运算过程
  • 需要定义一些辅助的基本函数
      //Hello   World => hello_world

      const f = fp.flowRight(fp.replace(/\s+/g, "_"), fp.toLower);

      console.log(f("Hello   World"));

      // world wild web ==>W. W. W

      //const firstLetterToUpper = fp.flowRight(fp.join(". "),fp.map(fp.first), fp.map(fp.toUpper), fp.split(" "));
      const firstLetterToUpper = fp.flowRight(fp.join(". "), fp.map(fp.flowRight(fp.first, fp.toUpper)), fp.split(" "));

Functor(函子)

  • 容器: 包含值和值的变形关系(这个变形关系就是函数)
  • 函子: 是一个特殊的容器,通过普通的对象来实现,该对象具有map方法,map方法可以运行一个函数对值进行处理(变形关系)
    class Container {
        constructor(value) {
            this._value = value;
        };

        map(fn) {
            return Container.of(fn(this._value));
        };
        static of (value) {
            return  new Container(value);
        };
    };

    const r = Container.of(5)
        .map(x => x + 2)
        .map(x => x * x);
MayBe函子
  • 可以对外部的空值做处理
class MayBe {
    static of (value) {
        return new MayBe(value);
    };
    constructor(value) {
        this._value = value;
    };
    map(fn) {
        return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this._value));
    };
    isNothing() {
        return this._value === null || this._value === undefined;
    };
};

Either函子

  • Either两者中的任何一个,类似于if…else… 的处理
  • 异常会让函数变的不纯洁,Either函子可以用来做异常处理
class Left {
    static of (value) {
        return new Left(value);
    };
    constructor(value) {
        this._value = value;
    };
    map(fn) {
        return this;
    };
};

class Right {
    static of (value) {
        return new Right(value);
    };
    constructor(value) {
        this._value = value;
    };
    map(fn) {
        return Right.of(fn(this._value));
    };
};

function parseJson(str) {
    try {
        return Right.of(str);
    } catch (e) {
        return Left.of({
            error: e.message
        });
    };
};

// const r = parseJson('{ name: zs }');
// console.log(r);

const r = parseJson('{ "name": "zs" }')
    .map(x => x.name.toUpperCase());
console.log(r);

IO函子

  • IO函子中的_value是一个函数,这里是把函数作为值来处理
  • IO函子可以把不纯的动作存储到_value中,延迟执行这个不纯的操作(惰性执行),包装当前的操作
  • 把不纯的操作交给调用者来处理
 class IO {
     static of (value) {
         return new IO(function() {
             return value;
         });
     };
     constructor(fn) {
         this._value = fn;
     };
     map(fn) {
         return new IO(fp.flowRight(fn, this._value));
     };
 };

Task异步执行

  • folktale一个标准的函数式编程库
    • 和lodash、ramada不同的是,他没有提供很多功能函数
    • 只提供了一些函数式处理的操作,例如:compose、curry等,一些函子Task、Either、MayBe等
//folktale
const {
    compose: ftCompose,
    curry: ftCurry
} = require("folktale/core/lambda");

const f = ftCurry(2, (x, y) => {
    return x + y;
});
// console.log(f(1,2));
// console.log(f(1)(2));

const f = ftCompose(fp.toUpper, fp.first);

//Task处理异步
const fs = require("fs");
const {
    task
} = require("folktale/concurrency/task");

function readFile(filename) {
    return task(resolver => {
        fs.readFile(filename, "utf-8", (err, data) => {
            if (err) {
                resolver.reject(err);
            };
            resolver.resolve(data);
        });
    });
};

readFile("package.json")
    .map(fp.split("\n"))
    .map(fp.find(x => x.includes("version")))
    .run()
    .listen({
        onRejected: err => {
            console.log(err);
        },
        onResolved: value => {
            console.log(value);
        }
    });

Pointed函子

  • Pointed函子是实现了of静态方法的函子
  • of方法是为了避免使用new来创建对象,更深层的含义是of方法用来把值放到上下文Context(把值放到容器中,使用map来处理值)

Monad函子

  • 是可以变扁的Pointed函子,IO(IO(x))
  • 一个函子如果具有join和of两个方法并遵守一些定律就是一个Monad
//IO函子的问题
// class IO {
//     static of(value) {
//         return new IO(function () {
//             return value;
//         });
//     };
//     constructor(fn) {
//         this._value = fn;
//     };
//     map(fn) {
//         return new IO(fp.flowRight(fn, this._value));
//     };
// };
class IO {
    static of (value) {
        return new IO(function() {
            return value;
        });
    };
    constructor(fn) {
        this._value = fn;
    };
    map(fn) {
        return new IO(fp.flowRight(fn, this._value));
    };
    join() {
        return this._value();
    };
    flatMap(fn) {
        return this.map(fn).join();
    }
};

const reaFile = function(filename) {
    return new IO(function() {
        return fs.readFileSync(filename, "utf-8");
    });
};

const print = function(x) {
    return new IO(function() {
        console.log(x);
        return x;
    });
};

const cat = fp.flowRight(print, readFile);
//IO(IO(x))
// const r = cat("package.json")._value()._value();
const r = reaFile("package.json")
    .flatMap(print)
    .join();
console.log(r);

总结

JavaScript不像其他语言,即使JavaScript有面向对象的使用方式,但那只不过是语法糖,归根结底还是使用了原型,只不过是用原型的方式来模拟了面向对象而已,现在不管是React还是Vue已经都开始使用函数式编程的思想,而我个人也觉得函数式编程才应该是JavaScript编写代码的核心所在,不过,也需要注意编写代码的时候,其实也不一定要刻意使用函数式编程,最重要的还是怎么方便怎么来。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值