• js-函数式编程总结-核心思想curry


    函数式编程

    函数式编程思想主要内容:

    • curry
    • 高阶函数
    • 递归
    • 纯函数
    • 流编程-pipe/compose
    • 无类编程
      • Container、functor-Maybe/IO/Task/Either-of/map/
      • Monad-join/chain/mcompose
      • Applicative Functor-ap

    学习方法:

    1. 库:目前有lodash、lodash/fp和ramda两个库,补充库有:futil-js
    2. 基本概念,看书。
    3. 如何使用,看库API。
    4. 实践-用起来,看项目例子,多多使用函数式编程思想指导尝试优化代码。
    5. 总结,看书,回归概念。
    6. 提升,再次带着概念看代码并尝试优化。

    函数式编程核心思想-curry:

    1. 为什么要使用curry
    2. 爱上柯里化
    3. 函数式指北-柯里化

    为什么要使用curry,例子:

    不使用curry时带来的问题,引入为什么要使用curry,curry有什么好处?

    // 不使用curry,代码长,且可读性不够好,不够语义化,想想html的语义化,这就是使用curry的其中一个目的。
    var objects = [{ id: 1 }, { id: 2 }, { id: 3 }]
    objects.map(function(o){ return o.id })
    // 多个属性调用时,需要写多个map函数。
    
    // 使用curry
    var get = curry(function(property, object){ return object[property] })
    objects.map(get('id')) //= [1, 2, 3]
    // 多个属性调用时,只需要确定属性即可。但代码不够语义化。
    
    // 更加语义化
    var getIDs = function(objects){
      return objects.map(get('id'))
    }
    getIDs(objects) //= [1, 2, 3]
    // 但id写死了,耦合度高。
    
    // 使用curry,解耦属性指定,函数可以灵活使用。
    var map = curry(function(fn, value){ return value.map(fn) })
    var getIDs = map(get('id'))
    getIDs(objects) //= [1, 2, 3]
    // 语义化,并且不会耦合。
    
    // 上面的推导很有意思,可以反复琢磨,让自己习惯函数式编程的思维。
    // 由于旧的编程思想已经形成了编程习惯,平时写代码时会习惯性的编写非函数式编程的代码。所以,我们需要经常练习并养成函数式编程思想的编码习惯。
    
    // 对比日常处理数据时,不使用curry和使用curry的写法:
    // 假设有请求到这样的数据:
    let data = {
        "user": "hughfdjackson",
        "posts": [
            { "title": "why curry?", "contents": "..." },
            { "title": "prototypes: the short(est possible) story", "contents": "..." }
        ]
    }
    // 想要对这个数据进行一系列的操作
    // 一般的写法
    fetchFromServer()
        .then(JSON.parse)
        .then(function(data){ return data.posts })
        .then(function(posts){
            return posts.map(function(post){ return post.title })
        })
    
    //  使用curry的写法,简洁而且语义清晰,代码逻辑和可读性强
    fetchFromServer()
        .then(JSON.parse)
        .then(get('posts'))
        .then(map(get('title')))
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    爱上curry-例子:(对比使用前后区别,突出curry优势)

    // 假设请求到数据:
    var data = {
        result: "SUCCESS",
        interfaceVersion: "1.0.3",
        requested: "10/17/2013 15:31:20",
        lastUpdated: "10/16/2013 10:52:39",
        tasks: [
            {id: 104, complete: false,            priority: "high",
                      dueDate: "2013-11-29",      username: "Scott",
                      title: "Do something",      created: "9/22/2013"},
            {id: 105, complete: false,            priority: "medium",
                      dueDate: "2013-11-22",      username: "Lena",
                      title: "Do something else", created: "9/22/2013"},
            {id: 107, complete: true,             priority: "high",
                      dueDate: "2013-11-22",      username: "Mike",
                      title: "Fix the foo",       created: "9/22/2013"},
            {id: 108, complete: false,            priority: "low",
                      dueDate: "2013-11-15",      username: "Punam",
                      title: "Adjust the bar",    created: "9/25/2013"},
            {id: 110, complete: false,            priority: "medium",
                      dueDate: "2013-11-15",      username: "Scott",
                      title: "Rename everything", created: "10/2/2013"},
            {id: 112, complete: true,             priority: "high",
                      dueDate: "2013-11-27",      username: "Lena",
                      title: "Alter all quuxes",  created: "10/5/2013"}
            // , ...
        ]
    };
    // 一般写法:
    getIncompleteTaskSummaries = function(membername) {
        return fetchData()
            .then(function(data) {
                return data.tasks;
            })
            .then(function(tasks) {
                var results = [];
                for (var i = 0, len = tasks.length; i < len; i++) {
                    if (tasks[i].username == membername) {
                        results.push(tasks[i]);
                    }
                }
                return results;
            })
            .then(function(tasks) {
                var results = [];
                for (var i = 0, len = tasks.length; i < len; i++) {
                    if (!tasks[i].complete) {
                        results.push(tasks[i]);
                    }
                }
                return results;
            })
            .then(function(tasks) {
                var results = [], task;
                for (var i = 0, len = tasks.length; i < len; i++) {
                    task = tasks[i];
                    results.push({
                        id: task.id,
                        dueDate: task.dueDate,
                        title: task.title,
                        priority: task.priority
                    })
                }
                return results;
            })
            .then(function(tasks) {
                tasks.sort(function(first, second) {
                    var a = first.dueDate, b = second.dueDate;
                    return a < b ? -1 : a > b ? 1 : 0;
                });
                return tasks;
            });
    };
    
    // curry的写法:
    var getIncompleteTaskSummaries = function(membername) {
      return fetchData()
          .then(R.get('tasks'))
          .then(R.filter(R.propEq('username', membername)))
          .then(R.reject(R.propEq('complete', true)))
          .then(R.map(R.pick(['id', 'dueDate', 'title', 'priority'])))
          .then(R.sortBy(R.get('dueDate')));
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84

    函数式指北-例子:

    var curry = require('lodash').curry;
    
    // 将原函数转化为curry函数
    var match = curry(function(what, str) {
      return str.match(what);
    });
    
    var replace = curry(function(what, replacement, str) {
      return str.replace(what, replacement);
    });
    
    var filter = curry(function(f, ary) {
      return ary.filter(f);
    });
    
    var map = curry(function(f, ary) {
      return ary.map(f);
    });
    
    // 生成中间函数
    var hasSpaces = match(/\s+/g);
    // function(x) { return x.match(/\s+/g) }
    
    // 1. 可以直接使用
    hasSpaces("hello world");
    // [ ' ' ]
    
    hasSpaces("spaceless");
    // null
    
    // 2. 生成的中间函数,作为其他函数的参数使用
    filter(hasSpaces, ["tori_spelling", "tori amos"]);
    // ["tori amos"]
    
    // 使用curry函数和中间函数生成更加具体的语义化函数
    var findSpaces = filter(hasSpaces);
    // function(xs) { return xs.filter(function(x) { return x.match(/\s+/g) }) }
    
    findSpaces(["tori_spelling", "tori amos"]);
    // ["tori amos"]
    
    // 生成语义化函数
    var noVowels = replace(/[aeiou]/ig);
    // function(replacement, x) { return x.replace(/[aeiou]/ig, replacement) }
    
    // 第二次传参,生成具体语义化函数
    var censored = noVowels("*");
    // function(x) { return x.replace(/[aeiou]/ig, "*") }
    
    censored("Chocolate Rain");
    // 'Ch*c*l*t* R**n'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    函数式指北-一对多转换

    var getChildren = function(x) {
      return x.childNodes;
    };
    var allTheChildren = map(getChildren);
    
    • 1
    • 2
    • 3
    • 4

    函数式指北-局部调用

    只传给函数一部分参数通常也叫做局部调用(partial application),能够大量减少样板文件代码

    // 预先设定一个或几个参数,参数的顺序是反过来的,即从参数顺序的后面开始入参。
    var allTheChildren = function(elements) {
      return _.map(elements, getChildren);
    };
    
    // 库写法
    // lodash库:
    var allTheChildren = _.partial(_.map, getChildren) // 反转参数顺序
    // ramda库:
    var allTheChildren = R.flip(R.map)(getChildren) // 反转参数顺序,并调用第一个参数。
    var allTheChildren = R.map(R.__, getChildren) // 使用R.__占位符,下次传参时会把参数放到占位符的位置上。
    // 上面几种写法意思都是反转参数顺序,效果相同
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    其他有趣的事情

    一个函数通过传入不同的值而生成不同的功能函数

    // curry的一个有趣应用
    // 生成一个二次方程函数
    var quadratic = R.curry((a, b, c, x) => x * x * a + x * b + c)
    // (a, 0, 0, x) => x * x * a + x * 0 + 0
    // (1, 0, 0, x) => 2 * 2
    // (1, 0, 0, x) => x ^ 2
    quadratic(1, 0, 0, 2); //=> 4
    quadratic(1, 0, 0)(2); //=> 4
    
    // 生成一个偏移量函数
    // (0, 1, -1, x) => x - 1
    var xOffset = quadratic(0, 1, -1)
    xOffset(0); //=> -1
    xOffset(1); //=> 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    函数式指北-curry练习

    var _ = require('ramda');
    
    
    // 练习 1
    //==============
    // 通过局部调用(partial apply)移除所有参数
    
    var words = function(str) {
      return split(' ', str);
    };
    
    // 练习 1a
    //==============
    // 使用 `map` 创建一个新的 `words` 函数,使之能够操作字符串数组
    
    var sentences = undefined;
    
    
    // 练习 2
    //==============
    // 通过局部调用(partial apply)移除所有参数
    
    var filterQs = function(xs) {
      return filter(function(x){ return match(/q/i, x);  }, xs);
    };
    
    
    // 练习 3
    //==============
    // 使用帮助函数 `_keepHighest` 重构 `max` 使之成为 curry 函数
    
    // 无须改动:
    var _keepHighest = function(x,y){ return x >= y ? x : y; };
    
    // 重构这段代码:
    var max = function(xs) {
      return reduce(function(acc, x){
        return _keepHighest(acc, x);
      }, -Infinity, xs);
    };
    
    
    // 彩蛋 1:
    // ============
    // 包裹数组的 `slice` 函数使之成为 curry 函数
    // //[1,2,3].slice(0, 2)
    var slice = undefined;
    
    
    // 彩蛋 2:
    // ============
    // 借助 `slice` 定义一个 `take` curry 函数,该函数调用后可以取出字符串的前 n 个字符。
    var take = undefined;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    curry总结:

    技术上:通过闭包暂存参数,从而生成语义化函数。
    定义:每次入参都会生成一个函数用于处理剩余的参数,curry函数会一直消耗参数,直到最后一次入参后,运行函数得到结果。
    特点:

    • curry函数是纯函数
    • 可以直接调用
    • 作为其他函数的参数使用
    • 函数构建函数,使函数语义化
    • 多次curry多用途,即中间函数可以被用作他途。
    • 可以简洁的处理一对多的场景,并且语义清晰。
    • 局部柯里化被Ramda库通过倒置参数实现控制反转的整体构建思想消灭掉了,虽然库里面还是保留了参数倒置API
  • 相关阅读:
    java优雅去除 NullPointerException 空指针异常
    【STC8A8K64D4开发板】——STC8A8K64D4开发板介绍
    怎么把flac音频变为mp3?
    java毕业生设计助农脱贫系统计算机源码+系统+mysql+调试部署+lw
    Codeforces Round 908 (Div 2——AB)
    【论文阅读-PRIVGUARD】Day4:3节
    Microsoft Fabric 是什么?
    Matlab:Matlab编程语言的简介、安装、学习路线(几十项代码编程案例分析)之详细攻略
    一键部署(dhcp、dns、pxe、raid、nfs+apache+expect、lvm、磁盘分区、监控资源)
    FPGA时序约束(七)文献时序约束实验测试
  • 原文地址:https://blog.csdn.net/junjiahuang/article/details/127647556