如何使用函数式编程?

由于最近面试的原因,一直没有更新博文,最近有时间了,谈一下一直在研究的函数式编程的使用。

函数式编程,从接触以来似乎没怎么使用,据说backbone的依赖是underscore,是不是也是函数式呢?redux不依赖underscore,是不是函数式呢?

backbone这个不清楚,反正在redux中或多或少的使用了函数式编程的理念,用到函数式编程(以下简称为fp)的compose函数来处理它的reducer。

那么fp到底该怎么用,它有什么好处呢?                  系统的学习fp可到这里来-->点我 当然,买本《functional javascript》也是不错的~

您也可以看一下我的这篇博文--《javascript柯里化及组合函数》里面讲了本例子所用到的主要函数~

-----------------------正文分割线-------------------------------------------

引用一句话说:我们要开始转变观念了,从现在开始,我们将不再指示计算机如何工作,而是指出我们明确希望得到的结果。

先举个栗子~

 这是命令式编程

var makes = [];
for (i = 0; i < cars.length; i++) {
  makes.push(cars[i].make);
}

这是声明式编程

var makes = cars.map(function(car){ return car.make; });

我觉得高低立分,声明式的map函数是一个表达式,直观而且自由度很大。我们可以随意更改makes的内容,而这些都是命令式达不到的。fp的优势就是这样。

从书上找一个例子来说明fp是怎样编程的:

这个例子是获取图片,然后展示在浏览器上。

这是html结构:

<!DOCTYPE html>
<html>
  <head>
    <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>

flickr.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'
  }
});

require([
    'ramda',
    'jquery'
  ],
  function (_, $) {
    var trace = _.curry(function(tag, x) {
      console.log(tag, x);
      return x;
    });
    // app goes here
  });

ramda是一个fp的库,就类似于underscore和lodash,本例子用了requirejs,虽然我不怎么喜欢这个框架,为保持一致性,就一直用require了。

这个flickr.js就是用require加载了ramda和jquery,声明了trace函数用来在fp的流中打印当前流的值,其实排错用,暂时忽略它。

本应用的任务呢?

  1. 根据特定搜索关键字构造 url
  2. 向 flickr 发送 api 请求
  3. 把返回的 json 转为 html 图片
  4. 把图片放到屏幕上

上面提到了2个不纯的动作:从api获取数据和把图片放到屏幕上,我们先把不纯的动作写出来,以便分离开其他纯的动作。

var Impure = {
  getJSON: _.curry(function(callback, url) {
    $.getJSON(url, callback);
  }),

  setHtml: _.curry(function(sel, html) {
    $(sel).html(html);
  })
};

在 Impure 命名空间下,这样我们就知道它们都是危险函数。

简单了封装了jquery的getJSON和jquery的html。就可以用Impure.getJSON(url,callback)来调用啦。

var url = function (term) {
  return 'https://api.flickr.com/services/feeds/photos_public.gne?tags=' + term + '&format=json&jsoncallback=?';
};

封装获得url的函数。

好,我们把方法都写完了,现在开始fp之旅。

利用compose组合了获取url和获取图片这个任务。app("cats");就可以得到cats图片的src,回掉函数是trace("response"),它会打印服务器的返回。

var app = _.compose(Impure.getJSON(trace("response")), url);

app("cats");

如果命令式编程的话会写为

var _url = url("cat");
Impure.getJSON(_url,trace("response"));

这样就不是那么简洁自由了。

这会调用 url 函数,然后把字符串传给 getJSON 函数。getJSON 已经局部应用了 trace,加载这个应用将会把请求的响应显示在 console 里。

得到这个json后,我们想要从这个 json 里构造图片,但是 src 都在 items 数组中的每个 media 对象的 m 属性上

然后实现一个辅助函数:

var prop = _.curry(function(property, object){
  return object[property];
});

这个其实就是_.prop了。很无聊是不是。。

然后呢,获取图片url,

var mediaUrl = _.compose(_.prop('m'), _.prop('media'));

var srcs = _.compose(_.map(mediaUrl), _.prop('items'));

从json里面取到items属性,然后取media属性,然后取m属性。。一系列的取值运算都用compose来执行。

然后把它整合到html里:

var renderImages = _.compose(Impure.setHtml("body"), srcs);
var app = _.compose(Impure.getJSON(renderImages), url);

app方法是把字符串通过url形成需要捕获的url字符串,然后放到getJSON里获取,然后返回renderImage方法。

renderImage函数是把得到的src传到body里,body里会显示图片的src。

这个方法是真正把图片添加到html里。

var img = function (url) {
  return $('<img />', { src: url });
};

这是真正render图片的renderImages方法。与上面的renderImages方法不一样的地方就是在setHtml的时候不是把srcs放到body了,它加了img标签了。

images就是把srcs组装成html里的img标签。

var images = _.compose(_.map(img), srcs);
var renderImages = _.compose(Impure.setHtml("body"), images);
var app = _.compose(Impure.getJSON(renderImages), url);

这样就完成了~

最后执行

app("cats");

的时候,这一系列工具链启动了,cats传入url里面封装成需要捕获的url,然后传到getJSON里面获取。getJSON的callback又是renderImages,renderImages把得到的src组装到img标签,然后扔到body里。然后这些工作就完成了。

只说做什么,不说怎么做。这就是fp。我们只传入了cats,这是我们需要的cat图片,然后怎么做呢,这些函数通过compose完成自己的功能。然后通过compose组合起来,完成这个项目的应用。

是不是觉得清晰了很多,而且每一步都是可变的,就像gulp执行pipe,增加一个流的处理是很简单的,对管道的流的处理可以改变整个结果。

 

完整代码:

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

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(renderImages), url);

    app("cats");
  });

 

posted @ 2016-04-18 19:24  chenby  阅读(927)  评论(0编辑  收藏  举报