@duanyubin
2016-05-31T01:48:58.000000Z
字数 4769
阅读 430
javascript
副作用是在计算结果的过程中,系统状态的一种变化,或者与外部世界进行的可观察的交互。
副作用可能包含,但不限于:
这并不是说,要禁止使用一切副作用,而是说,要让它们在可控的范围内发生。
函数式编程的哲学就是假定副作用是造成不正当行为的主要原因。
在日常的开发中,不可能没有dom查询和发送http请求,所以需要将这些不纯的函数包装起来,使之变纯。
var add = function(x) {
return function(y) {
return x + y;
};
};
var increment = add(1);
var addTen = add(10);
increment(2);
// 3
addTen(2);
// 12
体会curry
var curry = require('lodash').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);
});
//要操作的数据(String, Array)放到最后一个参数里
match(/\s+/g, "hello world");
// [ ' ' ]
match(/\s+/g)("hello world");
// [ ' ' ]
var hasSpaces = match(/\s+/g);
// function(x) { return x.match(/\s+/g) }
hasSpaces("hello world");
// [ ' ' ]
hasSpaces("spaceless");
// null
filter(hasSpaces, ["tori_spelling", "tori amos"]);
// ["tori amos"]
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'
是一种“预加载”函数的能力,通过传递一到两个参数调用函数,就能得到一个记住了这些参数的新函数。
当我们谈论纯函数的时候,我们说它们接受一个输入返回一个输出。curry 函数所做的正是这样:每传递一个参数调用函数,就返回一个新函数处理剩余的参数。这就是一个输入对应一个输出啊。
哪怕输出是另一个函数,它也是纯函数。
柯里化的这种特性,在与compose组合使用过程中,显现出它的优势。
想要一个函数比较通用,就必须使它的参数个数最少化。
var compose = function(f,g) {
return function(x) {
return f(g(x));
};
};
//f 和 g 都是函数,x 是在它们之间通过“管道”传输的值。
var toUpperCase = function(x) { return x.toUpperCase(); };
var exclaim = function(x) { return x + '!'; };
var shout = compose(exclaim, toUpperCase);
shout("send in the clowns");
//=> "SEND IN THE CLOWNS!"
g 将先于 f 执行,因此就创建了一个从右到左的数据流
var associative = compose(f, compose(g, h)) == compose(compose(f, g), h);
example:
var head = function(x) { return x[0]; };
var reverse = reduce(function(acc, x){ return [x].concat(acc); }, []);
var last = compose(head, reverse);
last(['jumpkick', 'roundhouse', 'uppercut']);
// 满足结合律,所以
compose(toUpperCase, compose(head, reverse));
// 或者
compose(compose(toUpperCase, head), reverse);
// 这表明调用分组不重要,所以有能力扩充compose方法
// 前面的例子中我们必须要写两个组合才行,但既然组合是符合结合律的,我们就可以只写一个,
// 而且想传给它多少个函数就传给它多少个,然后让它自己决定如何分组。
var lastUpper = compose(toUpperCase, head, reverse);
lastUpper(['jumpkick', 'roundhouse', 'uppercut']);
//=> 'UPPERCUT'
var loudLastUpper = compose(exclaim, toUpperCase, head, reverse)
loudLastUpper(['jumpkick', 'roundhouse', 'uppercut']);
//=> 'UPPERCUT!'
// 同样的
var loudLastUpper = compose(exclaim, toUpperCase, head, reverse);
// 或
var last = compose(head, reverse);
var loudLastUpper = compose(exclaim, toUpperCase, last);
// 或
var last = compose(head, reverse);
var angry = compose(exclaim, toUpperCase);
var loudLastUpper = compose(angry, last);
// 更多变种...
函数无须提及将要操作的数据是什么样的。一等公民的函数、柯里化(curry)以及组合协作起来非常有助于实现这种模式。
// 非 pointfree,因为提到了数据:word
var snakeCase = function (word) {
return word.toLowerCase().replace(/\s+/ig, '_');
};
// pointfree
var snakeCase = compose(replace(/\s+/ig, '_'), toLowerCase);
最直观的意义: pointfree 模式能够帮助我们减少不必要的命名,让代码保持简洁和通用
// 1. 隔离不纯的函数
var Impure = {
getJSON: _.curry(function(callback, url) {
$.getJSON(url, callback);
}),
setHtml: _.curry(function(sel, html) {
$(sel).html(html);
})
};
// 2.1 构造 url 传给 Impure.getJSON 函数
var url = function (term) {
return 'https://api.flickr.com/services/feeds/photos_public.gne?tags=' + term + '&format=json&jsoncallback=?';
};
// 2.2 测试flicker api
var app = _.compose(Impure.getJSON(trace("response")), url);
app("cats");
// 我们想要从这个 json 里构造图片,看起来 src 都在 items 数组中的每个 media 对象的 m 属性上。
// 3.1 实现一个 prop, 可以用ramda 的prop方法
var prop = _.curry(function(property, object){
return object[property];
});
// 3.2 利用这个函数获取图片的 src
var mediaUrl = _.compose(_.prop('m'), _.prop('media'));
var srcs = _.compose(_.map(mediaUrl), _.prop('items'));
// 4 结合图片src和setHtml,将图片地址打印到body里
var renderImages = _.compose(Impure.setHtml("body"), srcs);
var app = _.compose(Impure.getJSON(renderImages), url);
// 5 将src变为真正的图片
var img = function (url) {
return $('<img />', { src: url });
};
var images = _.compose(_.map(img), srcs);
var renderImages = _.compose(Impure.setHtml("body"), images);
var app = _.compose(Impure.getJSON(renderImages), url);
// map 的组合律
var law = compose(map(f), map(g)) == map(compose(f, g));
// 原有代码
var mediaUrl = _.compose(_.prop('m'), _.prop('media'));
var srcs = _.compose(_.map(mediaUrl), _.prop('items'));
var images = _.compose(_.map(img), srcs);
// 经过等式替换
var mediaUrl = _.compose(_.prop('m'), _.prop('media'));
var images = _.compose(_.map(img), _.map(mediaUrl), _.prop('items'));
// map组合律
var mediaUrl = _.compose(_.prop('m'), _.prop('media'));
var images = _.compose(_.map(_.compose(img, mediaUrl)), _.prop('items'));
// 提取map 调用的 compose 取出来放到外面,提高一下可读性。
var mediaUrl = _.compose(_.prop('m'), _.prop('media'));
var mediaToImg = _.compose(img, mediaUrl);
var images = _.compose(_.map(mediaToImg), _.prop('items'));