[关闭]
@duanyubin 2016-05-31T01:48:58.000000Z 字数 4769 阅读 370

Functional Programming

javascript


原文地址

再说纯函数

副作用

副作用是在计算结果的过程中,系统状态的一种变化,或者与外部世界进行的可观察的交互。

副作用可能包含,但不限于:

这并不是说,要禁止使用一切副作用,而是说,要让它们在可控的范围内发生。
函数式编程的哲学就是假定副作用是造成不正当行为的主要原因。
在日常的开发中,不可能没有dom查询和发送http请求,所以需要将这些不纯的函数包装起来,使之变纯。

currying

why curry help

  1. var add = function(x) {
  2. return function(y) {
  3. return x + y;
  4. };
  5. };
  6. var increment = add(1);
  7. var addTen = add(10);
  8. increment(2);
  9. // 3
  10. addTen(2);
  11. // 12

体会curry

  1. var curry = require('lodash').curry;
  2. var match = curry(function(what, str) {
  3. return str.match(what);
  4. });
  5. var replace = curry(function(what, replacement, str) {
  6. return str.replace(what, replacement);
  7. });
  8. var filter = curry(function(f, ary) {
  9. return ary.filter(f);
  10. });
  11. var map = curry(function(f, ary) {
  12. return ary.map(f);
  13. });
  14. //要操作的数据(String, Array)放到最后一个参数里
  15. match(/\s+/g, "hello world");
  16. // [ ' ' ]
  17. match(/\s+/g)("hello world");
  18. // [ ' ' ]
  19. var hasSpaces = match(/\s+/g);
  20. // function(x) { return x.match(/\s+/g) }
  21. hasSpaces("hello world");
  22. // [ ' ' ]
  23. hasSpaces("spaceless");
  24. // null
  25. filter(hasSpaces, ["tori_spelling", "tori amos"]);
  26. // ["tori amos"]
  27. var findSpaces = filter(hasSpaces);
  28. // function(xs) { return xs.filter(function(x) { return x.match(/\s+/g) }) }
  29. findSpaces(["tori_spelling", "tori amos"]);
  30. // ["tori amos"]
  31. var noVowels = replace(/[aeiou]/ig);
  32. // function(replacement, x) { return x.replace(/[aeiou]/ig, replacement) }
  33. var censored = noVowels("*");
  34. // function(x) { return x.replace(/[aeiou]/ig, "*") }
  35. censored("Chocolate Rain");
  36. // 'Ch*c*l*t* R**n'

是一种“预加载”函数的能力,通过传递一到两个参数调用函数,就能得到一个记住了这些参数的新函数。
当我们谈论纯函数的时候,我们说它们接受一个输入返回一个输出。curry 函数所做的正是这样:每传递一个参数调用函数,就返回一个新函数处理剩余的参数。这就是一个输入对应一个输出啊。
哪怕输出是另一个函数,它也是纯函数。
柯里化的这种特性,在与compose组合使用过程中,显现出它的优势。
想要一个函数比较通用,就必须使它的参数个数最少化。

compose

  1. var compose = function(f,g) {
  2. return function(x) {
  3. return f(g(x));
  4. };
  5. };
  6. //f 和 g 都是函数,x 是在它们之间通过“管道”传输的值。
  7. var toUpperCase = function(x) { return x.toUpperCase(); };
  8. var exclaim = function(x) { return x + '!'; };
  9. var shout = compose(exclaim, toUpperCase);
  10. shout("send in the clowns");
  11. //=> "SEND IN THE CLOWNS!"

g 将先于 f 执行,因此就创建了一个从右到左的数据流

结合律

  1. var associative = compose(f, compose(g, h)) == compose(compose(f, g), h);

example:

  1. var head = function(x) { return x[0]; };
  2. var reverse = reduce(function(acc, x){ return [x].concat(acc); }, []);
  3. var last = compose(head, reverse);
  4. last(['jumpkick', 'roundhouse', 'uppercut']);
  5. // 满足结合律,所以
  6. compose(toUpperCase, compose(head, reverse));
  7. // 或者
  8. compose(compose(toUpperCase, head), reverse);
  9. // 这表明调用分组不重要,所以有能力扩充compose方法
  10. // 前面的例子中我们必须要写两个组合才行,但既然组合是符合结合律的,我们就可以只写一个,
  11. // 而且想传给它多少个函数就传给它多少个,然后让它自己决定如何分组。
  12. var lastUpper = compose(toUpperCase, head, reverse);
  13. lastUpper(['jumpkick', 'roundhouse', 'uppercut']);
  14. //=> 'UPPERCUT'
  15. var loudLastUpper = compose(exclaim, toUpperCase, head, reverse)
  16. loudLastUpper(['jumpkick', 'roundhouse', 'uppercut']);
  17. //=> 'UPPERCUT!'
  18. // 同样的
  19. var loudLastUpper = compose(exclaim, toUpperCase, head, reverse);
  20. // 或
  21. var last = compose(head, reverse);
  22. var loudLastUpper = compose(exclaim, toUpperCase, last);
  23. // 或
  24. var last = compose(head, reverse);
  25. var angry = compose(exclaim, toUpperCase);
  26. var loudLastUpper = compose(angry, last);
  27. // 更多变种...

pointfree

函数无须提及将要操作的数据是什么样的。一等公民的函数、柯里化(curry)以及组合协作起来非常有助于实现这种模式。

  1. // 非 pointfree,因为提到了数据:word
  2. var snakeCase = function (word) {
  3. return word.toLowerCase().replace(/\s+/ig, '_');
  4. };
  5. // pointfree
  6. var snakeCase = compose(replace(/\s+/ig, '_'), toLowerCase);

最直观的意义: pointfree 模式能够帮助我们减少不必要的命名,让代码保持简洁和通用

示例

  1. 根据特定搜索关键字构造 url
  2. 向 flickr 发送 api 请求
  3. 把返回的 json 转为 html 图片
  4. 把图片放到屏幕上
    代码地址
  1. // 1. 隔离不纯的函数
  2. var Impure = {
  3. getJSON: _.curry(function(callback, url) {
  4. $.getJSON(url, callback);
  5. }),
  6. setHtml: _.curry(function(sel, html) {
  7. $(sel).html(html);
  8. })
  9. };
  10. // 2.1 构造 url 传给 Impure.getJSON 函数
  11. var url = function (term) {
  12. return 'https://api.flickr.com/services/feeds/photos_public.gne?tags=' + term + '&format=json&jsoncallback=?';
  13. };
  14. // 2.2 测试flicker api
  15. var app = _.compose(Impure.getJSON(trace("response")), url);
  16. app("cats");

此处输入图片的描述

  1. // 我们想要从这个 json 里构造图片,看起来 src 都在 items 数组中的每个 media 对象的 m 属性上。
  2. // 3.1 实现一个 prop, 可以用ramda 的prop方法
  3. var prop = _.curry(function(property, object){
  4. return object[property];
  5. });
  6. // 3.2 利用这个函数获取图片的 src
  7. var mediaUrl = _.compose(_.prop('m'), _.prop('media'));
  8. var srcs = _.compose(_.map(mediaUrl), _.prop('items'));
  9. // 4 结合图片src和setHtml,将图片地址打印到body里
  10. var renderImages = _.compose(Impure.setHtml("body"), srcs);
  11. var app = _.compose(Impure.getJSON(renderImages), url);
  12. // 5 将src变为真正的图片
  13. var img = function (url) {
  14. return $('<img />', { src: url });
  15. };
  16. var images = _.compose(_.map(img), srcs);
  17. var renderImages = _.compose(Impure.setHtml("body"), images);
  18. var app = _.compose(Impure.getJSON(renderImages), url);

组合律

  1. // map 的组合律
  2. var law = compose(map(f), map(g)) == map(compose(f, g));

代码

  1. // 原有代码
  2. var mediaUrl = _.compose(_.prop('m'), _.prop('media'));
  3. var srcs = _.compose(_.map(mediaUrl), _.prop('items'));
  4. var images = _.compose(_.map(img), srcs);
  5. // 经过等式替换
  6. var mediaUrl = _.compose(_.prop('m'), _.prop('media'));
  7. var images = _.compose(_.map(img), _.map(mediaUrl), _.prop('items'));
  8. // map组合律
  9. var mediaUrl = _.compose(_.prop('m'), _.prop('media'));
  10. var images = _.compose(_.map(_.compose(img, mediaUrl)), _.prop('items'));
  11. // 提取map 调用的 compose 取出来放到外面,提高一下可读性。
  12. var mediaUrl = _.compose(_.prop('m'), _.prop('media'));
  13. var mediaToImg = _.compose(img, mediaUrl);
  14. var images = _.compose(_.map(mediaToImg), _.prop('items'));
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注