Maison  >  Article  >  interface Web  >  Programmation fonctionnelle JavaScript (3)

Programmation fonctionnelle JavaScript (3)

黄舟
黄舟original
2017-03-06 14:02:541076parcourir

Dans le deuxième article, nous avons présenté plusieurs foncteurs courants tels que Peut-être, Soit, IO, peut-être De nombreuses personnes qui lisent le deuxième article aura des doutes :

"A quoi servent ces choses ?" 』

En fait, si vous souhaitez simplement apprendre à écrire du code fonctionnel avec de petits effets secondaires, la lecture du premier article suffit. Le deuxième article et le troisième ici se concentrent sur certaines pratiques de la théorie fonctionnelle, et oui, celles-ci sont difficiles (mais pas impossibles) à appliquer en production réelle, car de nombreuses roues ont déjà été construites et sont très faciles à mettre en œuvre. Par exemple, la spécification d'appel asynchrone de Promise, qui est désormais largement utilisée sur le front-end, est en fait une sorte de Monade (nous en parlerons plus tard) ; le Redux de plus en plus mature est implémenté comme une variante de Flux , et le concept de base est également les machines à états et la programmation fonctionnelle.

1. Monad

Il existe une infinité d'introductions et de didacticiels sur Monad sur Internet. De nombreux articles sont meilleurs que le mien ci-dessous, j'en utilise donc un plus simple ici, présenté de manière simple. -à comprendre Bien sûr, l'inconvénient d'être facile à comprendre est qu'il n'est pas rigoureux, alors pardonnez-moi/w

Si vous connaissez la spécification de Promise, vous devriez vous souvenir d'une fonctionnalité étonnante. de Promise :

doSomething()
    .then(result => {
        // 你可以return一个Promise链!
        return fetch('url').then(result => parseBody(result));
    })
    .then(result => {
        // 这里的result是上面那个Promise的终值
    })

doSomething()
    .then(result => {
        // 也可以直接return一个具体的值!
        return 123;
    })
    .then(result => {
        // result === 123
    })


Pour une fonction de rappel de Promise, elle peut soit renvoyer directement une valeur, soit renvoyer une nouvelle Promesse, mais pour leurs fonctions de rappel ultérieures En d'autres termes , les deux sont équivalents, ce qui résout parfaitement l'enfer d'imbrication longtemps critiqué dans nodejs.

En fait, Promise est une sorte de Monade Oui, vous devrez peut-être écrire beaucoup de Promesses chaque jour, mais jusqu'à présent, vous ne saviez pas que cette chose que vous utilisez quotidiennement est en fait fonctionnelle. concept qui semble très élevé.

Maintenant, implémentons une Monade. Si vous ne voulez pas la lire, rappelez-vous simplement "La promesse est une sorte de Monade" et sautez simplement ce chapitre.

Écrivons une fonction cat La fonction de cette fonction est la même que cat sous la ligne de commande Linux. Elle lit un fichier puis imprime le contenu de. le fichier. Pour l'implémentation de IO ici, merci de vous référer à l'article précédent :

import fs from 'fs';
import _ from 'lodash';

var map = _.curry((f, x) => x.map(f));
var compose = _.flowRight;

var readFile = function(filename) {
    return new IO(_ => fs.readFileSync(filename, 'utf-8'));
};

var print = function(x) {
    return new IO(_ => {
        console.log(x);
        return x;
    });
}

var cat = compose(map(print), readFile);

cat("file")
//=> IO(IO("file的内容"))

Puisque deux IO sont impliquées ici : la lecture des fichiers et l'impression, le résultat final est que nous obtenez deux couches d'IO, si vous voulez l'exécuter, vous ne pouvez appeler :

cat("file").__value().__value();
//=> 读取文件并打印到控制台

C'est embarrassant, non ? , alors devons-nous écrire en continu ?

Bien sûr, cela ne peut pas être inélégant. Implémentons une méthode de jointure. Sa fonction est de décoller une couche de Functor et de nous exposer le contenu :

var join = x => x.join();
IO.prototype.join = function() {
  return this.__value ? IO.of(null) : this.__value();
}

// 试试看
var foo = IO.of(IO.of('123'));

foo.join();
//=> IO('123')

Avec la jointure. méthode Après cela, cela devient un peu plus élégant : la méthode

var cat = compose(join, map(print), readFile);
cat("file").__value();
//=> 读取文件并打印到控制台


join peut be Les foncteurs sont aplatis, et nous appelons généralement les foncteurs ayant cette capacité des monades.

Voici juste une suppression très simple d'une couche d'emballage Functor, mais en tant que programmeurs élégants, nous ne pouvons pas toujours appeler manuellementmap 🎜>join pour supprimer les emballages redondants, sinon le code ressemblera à ceci :

var doSomething = compose(join, map(f), join, map(g), join, map(h));
Nous avons donc besoin d'une méthode appelée chain pour réaliser l'appel de chaîne que nous attendons, elle sera appelée Après map, join est automatiquement appelé pour supprimer les emballages redondants C'est aussi une fonctionnalité majeure de Monad :

var chain = _.curry((f, functor) => functor.chain(f));
IO.prototype.chain = function(f) {
  return this.map(f).join();
}

// 现在可以这样调用了
var doSomething = compose(chain(f), chain(g), chain(h));

// 当然,也可以这样
someMonad.chain(f).chain(g).chain(h)

// 写成这样是不是很熟悉呢?
readFile('file')
    .chain(x => new IO(_ => {
        console.log(x);
        return x;
    }))
    .chain(x => new IO(_ => {
        // 对x做一些事情,然后返回
    }))


Haha, vous l'avez peut-être remarqué. , la

chaîne n'est-elle pas similaire à puis dans Promise ? Oui, leur comportement est en effet cohérent (alors aura un peu plus de logique, il enregistrera le nombre de niveaux d'imbrication et fera la distinction entre Promise et les valeurs de retour ordinaires), et Promise est bien une pensée fonctionnelle.

(Au départ, je voulais utiliser Promise comme exemple pour écrire quelques exemples ci-dessous, mais je suppose que tous ceux qui peuvent voir ici devraient être capables d'écrire diverses chaînes Promise avec compétence, donc je n'écrirai pas 0w0)

En bref, Monad nous permet d'éviter l'enfer de l'imbrication et peut facilement effectuer une programmation fonctionnelle profondément imbriquée, comme les IO et d'autres tâches asynchrones.

2. Application de la programmation fonctionnelle

D'accord, c'est tout pour l'introduction de quelques théories de base de la programmation fonctionnelle. Si vous voulez en savoir plus, il est effectivement recommandé d'apprendre Haskell ou Lisp. un langage fonctionnel plus orthodoxe. Répondons à une question : à quoi sert la programmation fonctionnelle dans des applications pratiques ?

1. React

React est visible partout maintenant Si vous demandez pourquoi il est populaire, certaines personnes peuvent dire qu'il a de « bonnes performances », « cool ». ", " "Composants tiers riches", "nouveau", etc., mais ce ne sont pas les plus critiques. Le plus important est que React apporte de nouveaux concepts au développement front-end : les machines fonctionnelles et d'état.

Voyons comment écrire un « composant pur » dans React :

var Text = props => (
    <p style={props.style}>{props.text}</p>
)

咦这不就是纯函数吗?对于任意的 text 输入,都会产生唯一的固定输出,只不过这个输出是一个 virtual DOM 的元素罢了。配合状态机,就大大简化了前端开发的复杂度:

state => virtual DOM => 真实 DOM

在 Redux 中更是可以把核心逻辑抽象成一个纯函数 reducer:

reducer(currentState, action) => newState


关于 React+Redux(或者其它FLUX架构)就不在这里介绍太多了,有兴趣的可以参考相关的教程。

2、Rxjs

Rxjs 从诞生以来一直都不温不火,但它函数响应式编程(Functional Reactive Programming,FRP)的理念非常先进,虽然或许对于大部分应用环境来说,外部输入事件并不是太频繁,并不需要引入一个如此庞大的 FRP 体系,但我们也可以了解一下它有哪些优秀的特性。

在 Rxjs 中,所有的外部输入(用户输入、网络请求等等)都被视作一种 『事件流』:

--- 用户点击了按钮 --> 网络请求成功 --> 用户键盘输入 --> 某个定时事件发生 --> ......

举个最简单的例子,下面这段代码会监听点击事件,每 2 次点击事件产生一次事件响应:

var clicks = Rx.Observable
    .fromEvent(document, &#39;click&#39;)
    .bufferCount(2)
    .subscribe(x => console.log(x)); // 打印出前2次点击事件


其中 bufferCount 对于事件流的作用是这样的:

是不是很神奇呢?Rxjs 非常适合游戏、编辑器这种外部输入极多的应用,比如有的游戏可能有『搓大招』这个功能,即监听用户一系列连续的键盘、鼠标输入,比如上上下下左右左右BABA,不用事件流的思想的话,实现会非常困难且不优雅,但用 Rxjs 的话,就只是维护一个定长队列的问题而已:

var inputs = [];
var clicks = Rx.Observable
    .fromEvent(document, &#39;keydown&#39;)
    .scan((acc, cur) => {
        acc.push(cur.keyCode);
        var start = acc.length - 12 < 0 ? 0 : acc.length - 12;
        return acc.slice(start);
    }, inputs)
    .filter(x => x.join(&#39;,&#39;) == [38, 38, 40, 40, 37, 39, 37, 39, 66, 65, 66, 65].join(&#39;,&#39;))// 上上下下左右左右BABA,这里用了比较奇技淫巧的数组对比方法
    .subscribe(x => console.log(&#39;!!!!!!ACE!!!!!!&#39;));


当然,Rxjs 的作用远不止于此,但可以从这个范例里看出函数响应式编程的一些优良的特性。

3、Cycle.js

Cycle.js 是一个基于 Rxjs 的框架,它是一个彻彻底底的 FRP 理念的框架,和 React 一样支持 virtual DOM、JSX 语法,但现在似乎还没有看到大型的应用经验。

本质的讲,它就是在 Rxjs 的基础上加入了对 virtual DOM、容器和组件的支持,比如下面就是一个简单的『开关』按钮:

import xs from &#39;xstream&#39;;
import {run} from &#39;@cycle/xstream-run&#39;;
import {makeDOMDriver} from &#39;@cycle/dom&#39;;
import {html} from &#39;snabbdom-jsx&#39;;

function main(sources) {
  const sinks = {
    DOM: sources.DOM.select(&#39;input&#39;).events(&#39;click&#39;)
      .map(ev => ev.target.checked)
      .startWith(false)
      .map(toggled =>
        <p>
          <input type="checkbox" /> Toggle me
          <p>{toggled ? &#39;ON&#39; : &#39;off&#39;}</p>
        </p>
      )
  };
  return sinks;
}

const drivers = {
  DOM: makeDOMDriver(&#39;#app&#39;)
};

run(main, drivers);


当然,Cycle.js 这种『侵入式』的框架适用性不是太广,因为使用它就意味着应用中必须全部或者大部分都要围绕它的理念设计,这对于大规模应用来说反而是负担。

三、总结

既然是完结篇,那我们来总结一下这三篇文章究竟讲了些啥?

第一篇文章里,介绍了纯函数、柯里化、Point Free、声明式代码和命令式代码的区别,你可能忘记得差不多了,但只要记住『函数对于外部状态的依赖是造成系统复杂性大大提高的主要原因』以及『让函数尽可能地纯净』就行了。

第二篇文章,或许是最没有也或许是最有干货的一篇,里面介绍了『容器』的概念和 MaybeEitherIO 这三个强大的 Functor。是的,大多数人或许都没有机会在生产环境中自己去实现这样的玩具级 Functor,但通过了解它们的特性会让你产生对于函数式编程的意识。

软件工程上讲『没有银弹』,函数式编程同样也不是万能的,它与烂大街的 OOP 一样,只是一种编程范式而已。很多实际应用中是很难用函数式去表达的,选择 OOP 亦或是其它编程范式或许会更简单。但我们要注意到函数式编程的核心理念,如果说 OOP 降低复杂度是靠良好的封装、继承、多态以及接口定义的话,那么函数式编程就是通过纯函数以及它们的组合、柯里化、Functor 等技术来降低系统复杂度,而 React、Rxjs、Cycle.js 正是这种理念的代言人,这可能是大势所趋,也或许是昙花一现,但不妨碍我们去多掌握一种编程范式嘛0w0

以上就是JavaScript函数式编程(三)的内容,更多相关内容请关注PHP中文网(www.php.cn)!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn