@duanyubin
2016-05-31T01:48:58.000000Z
字数 4769
阅读 513
javascript
副作用是在计算结果的过程中,系统状态的一种变化,或者与外部世界进行的可观察的交互。
副作用可能包含,但不限于:
这并不是说,要禁止使用一切副作用,而是说,要让它们在可控的范围内发生。
函数式编程的哲学就是假定副作用是造成不正当行为的主要原因。
在日常的开发中,不可能没有dom查询和发送http请求,所以需要将这些不纯的函数包装起来,使之变纯。
var add = function(x) {return function(y) {return x + y;};};var increment = add(1);var addTen = add(10);increment(2);// 3addTen(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");// nullfilter(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,因为提到了数据:wordvar snakeCase = function (word) {return word.toLowerCase().replace(/\s+/ig, '_');};// pointfreevar 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 apivar 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 利用这个函数获取图片的 srcvar 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'));