函数式编程(三):组合函数
其他:
前面说到 curry,而 curry 与 compose(组合)是一对好基友,curry 函数的很大价值体现在它对于 compose 的友好性。组合的思想在于把小的单元逻辑合成一个程序,1+1>2。
在数学里,函数 f 和 g 的组合定义为 f( g(x) ),在 JavaScript 中就是这样了
var compose = function(f, g) { return function(x) { return f(g(x)); }; };
可以看成 x 在函数 f,g之间通过“管道”传输,来看一个例子。
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']); //=> 'uppercut'
你是否注意到 reverse 函数最先被应用?这很重要,函数是从右向左应用的。(从右往左执行更能反映数学上的定义)。组合函数有一个强大的特性——结合律。这意味着任何一个函数分组都可以拆开来,然后再以它们自己的组合方式打包在一起,这给我们带来了强大的灵活性。
var toUpperCase = function(x) { return x.toUpperCase(); }; var exclaim = function(x) { return x + '!'; }; compose(toUpperCase, compose(head, reverse)); // 或者 compose(compose(toUpperCase, head), reverse); //因为如何为 compose 的调用分组不重要,所以结果都是一样的。这也让我们有//能力写一个可变的组合(variadic 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 last = compose(head, reverse); var angry = compose(exclaim, toUpperCase); var loudLastUpper = compose(angry, last); // 更多变种...
关于如何组合,并没有标准的答案——我们可以按自己喜欢的方式搭乐高积木。一般最佳实践是让组合可重用。
#compose与curry
来看一个例子:
HTML代码部分:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.1.11/require.min.js"></script> <script src='flickr.js'></script> </head> <body> </body> </html>
js部分:
requirejs.config({ paths: { ramda: 'https://cdnjs.cloudflare.com/ajax/libs/ramda/0.13.0/ramda.min', jquery: 'https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min' } }); // prop 函数的实现 // var prop = _.curry(function(property, object){ // return object[property]; // }); require([ 'ramda', 'jquery' ], function(_, $) { //utils var Impure = { getJSON: _.curry(function(callback, url) { $.getJSON(url, callback); }), setHtml: _.curry(function(sel, html) { $(sel).html(html); }) }; var img = function(url) { return $('<img/>', { src: url }); }; var trace = _.curry(function(tag, x) { console.log(tag, x); return x; }); var url = function(t) { return 'https://api.flickr.com/services/feeds/photos_public.gne?tags=' + t + '&format=json&jsoncallback=?'; }; var mediaUrl = _.compose(_.prop('m'), _.prop('media')); var srcs = _.compose(_.map(mediaUrl), _.prop('items')); var images = _.compose(_.map(img), srcs); var renderImages = _.compose(Impure.setHtml('body'), images); var app = _.compose(Impure.getJSON(trace(renderImages), url); // var app = _.compose(Impure.getJSON(trace(trace('response')), url); app('cats'); });
通过 curry 与 组合 搭建起一个简单的应用,请求 Flickr 图片,并加载到页面上。。