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')))

爱上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')));
};

函数式指北-例子:

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'

函数式指北-一对多转换

var getChildren = function(x) {
  return x.childNodes;
};
var allTheChildren = map(getChildren);

函数式指北-局部调用

只传给函数一部分参数通常也叫做局部调用(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.__占位符,下次传参时会把参数放到占位符的位置上。
// 上面几种写法意思都是反转参数顺序,效果相同

其他有趣的事情

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

// 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

函数式指北-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;

curry总结:

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

  • curry函数是纯函数
  • 可以直接调用
  • 作为其他函数的参数使用
  • 函数构建函数,使函数语义化
  • 多次curry多用途,即中间函数可以被用作他途。
  • 可以简洁的处理一对多的场景,并且语义清晰。
  • 局部柯里化被Ramda库通过倒置参数实现控制反转的整体构建思想消灭掉了,虽然库里面还是保留了参数倒置API
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值