Langage de programmation fonctionnel
Les langages de programmation fonctionnels sont ceux qui facilitent l'utilisation de paradigmes de programmation fonctionnelle. En termes simples, s'il possède les caractéristiques requises pour la programmation fonctionnelle, il peut être qualifié de langage fonctionnel. Dans la plupart des cas, le style de programmation détermine réellement si un programme est fonctionnel.
Qu'est-ce qui rend un langage fonctionnel ?
La programmation fonctionnelle ne peut pas être implémentée en langage C. La programmation fonctionnelle ne peut pas être implémentée en Java (à l'exclusion de celles qui se rapprochent de la programmation fonctionnelle via de nombreuses solutions de contournement). Ces langages ne contiennent pas de constructions pour prendre en charge la programmation fonctionnelle. Ce sont des langages purement orientés objet et strictement non fonctionnels.
Dans le même temps, la programmation orientée objet ne peut pas être utilisée dans des langages purement fonctionnels, tels que Scheme, Haskell et Lisp.
Cependant, certaines langues prennent en charge les deux modes. Python est un exemple célèbre, mais il en existe d’autres : Ruby, Julia et, plus intéressant encore, Javascript. Comment ces langages prennent-ils en charge ces deux modèles de conception si différents ? Ils contiennent des fonctionnalités requises par les deux paradigmes de programmation. Cependant, pour Javascript, les caractéristiques fonctionnelles semblent cachées.
Mais en fait, les langages fonctionnels nécessitent un peu plus que ce qui précède. Quelles sont les caractéristiques des langages fonctionnels ?
特点 | 命令式 | 函数式 |
---|---|---|
编程风格 | 一步一步地执行,并且要管理状态的变化 | 描述问题和和所需的数据变化以解决问题 |
状态变化 | 很重要 | 不存在 |
执行顺序 | 很重要 | 不太重要 |
主要的控制流 | 循环、条件、函数调用 | 函数调用和递归 |
主要的操作单元 | 结构体和类对象 | 函数作为一等公民的对象和数据集 |
La syntaxe des langages fonctionnels doit prendre en compte des modèles de conception spécifiques, tels que les systèmes d'inférence de types et les fonctions anonymes. En général, le langage doit implémenter le calcul lambda. Et la stratégie d'évaluation de l'interprète doit être non stricte, d'appel à la demande (également appelée exécution paresseuse), ce qui permet des structures de données immuables et une évaluation paresseuse non stricte.
译注:这一段用了一些函数式编程的专业词汇。lambda演算是一套函数推演的形式化系统(听起来很晕), 它的先决条件是内部函数和匿名函数。非严格求值和惰性求值差不多一个意思,就是并非严格地按照运算规则把所有元素先计算一遍, 而是根据最终的需求只计算有用的那一部分,比如我们要取有一百个元素的数组的前三项, 那惰性求值实际只会计算出一个具有三个元素是数组,而不会先去计算那个一百个元素的数组。
Avantages
La programmation fonctionnelle sera une énorme inspiration lorsque vous la maîtriserez enfin. Ce type d'expérience fera passer votre future carrière de programmeur à un niveau supérieur, que vous deveniez ou non un programmeur fonctionnel à temps plein.
Mais nous ne parlons pas de comment apprendre à méditer maintenant ; nous parlons de comment apprendre un outil très utile qui fera de vous un meilleur programmeur.
Dans l'ensemble, quels sont les réels avantages pratiques de l'utilisation de la programmation fonctionnelle ?
Code du nettoyeur
La programmation fonctionnelle est plus propre, plus simple et plus petite. Cela simplifie le débogage, les tests et la maintenance.
Par exemple, nous avons besoin d'une fonction qui convertit un tableau à deux dimensions en un tableau à une dimension. Si on n'utilisait que des techniques impératives, on l'écrirait ainsi :
function merge2dArrayIntoOne(arrays) { var count = arrays.length; var merged = new Array(count); var c = 0; for (var i = 0; i < count; ++i) { for (var j = 0, jlen = arrays[i].length; j < jlen; ++j) { merged[c++] = arrays[i][j]; } } return merged }
Maintenant en utilisant des techniques fonctionnelles, cela peut s'écrire ainsi :
merge2dArrayIntoOne2 = (arrays) -> arrays.reduce (memo, item) -> memo.concat item , []
var merge2dArrayIntoOne2 = function(arrays) { return arrays.reduce( function(p,n){ return p.concat(n); }, []); };
译注:原著中代码有误,调用reduce函数时少了第二个参数空数组,这里已经补上。
Les deux fonctions prennent les mêmes entrées et renvoient la même sortie, mais l'exemple fonctionnel est plus propre.
Modulaire
La programmation fonctionnelle vous oblige à diviser les gros problèmes en cas plus petits qui résolvent le même problème, ce qui signifie que le code sera plus modulaire. Les programmes modulaires ont des descriptions plus claires, sont plus faciles à déboguer et plus simples à maintenir. Les tests seront également plus faciles car l'exactitude du code de chaque module peut être vérifiée indépendamment.
Réutilisabilité
En raison de sa nature modulaire, la programmation fonctionnelle possède de nombreuses fonctions auxiliaires communes. Vous constaterez que de nombreuses fonctions ici peuvent être réutilisées dans un certain nombre d'applications différentes.
Dans les chapitres suivants, bon nombre des fonctions les plus courantes seront couvertes. Cependant, en tant que programmeur fonctionnel, vous écrirez inévitablement votre propre bibliothèque de fonctions qui sera utilisée encore et encore. Par exemple, une fonction utilisée pour rechercher des fichiers de configuration entre les lignes peut également être utilisée pour rechercher une table de hachage si elle est bien conçue.
Réduire l'accouplement
Le couplage est un grand nombre de dépendances entre modules dans un programme. Puisque la programmation fonctionnelle suit l’écriture de fonctions pures de première classe, d’ordre supérieur, qui n’ont aucun effet secondaire sur les variables globales et sont complètement indépendantes les unes des autres, le couplage est considérablement réduit. Bien entendu, les fonctions dépendront inévitablement les unes des autres, mais la modification d’une fonction n’affectera pas les autres, tant que la correspondance biunivoque des entrées et des sorties reste correcte.
Correction mathématique
Le dernier point est plus théorique. Parce qu’elle est ancrée dans le calcul lambda, la programmation fonctionnelle peut s’avérer mathématiquement correcte. Il s’agit d’un énorme avantage pour certains chercheurs qui ont besoin de programmes pour prouver les taux de croissance, la complexité temporelle et l’exactitude mathématique.
Jetons un coup d’œil à la séquence de Fibonacci. Bien qu’il soit rarement utilisé en dehors des problèmes de preuve de concept, c’est un excellent moyen d’expliquer le concept. La manière standard d'évaluer une séquence de Fibonacci est de créer une fonction récursive, comme celle-ci :
fibonnaci(n) = fibonnaci(n-2) + fibonnaci(n–1)
Vous devez également ajouter une situation générale :
return 1 when n < 2
Cela permet à la récursion de se terminer et de permettre à chaque étape de la pile d'appels récursifs de s'accumuler à partir d'ici.
Les étapes détaillées sont répertoriées ci-dessous
var fibonacci = function(n) { if (n < 2) { return 1; }else { return fibonacci(n - 2) + fibonacci(n - 1); } } console.log( fibonacci(8) ); // Output: 34
Cependant, à l'aide d'une bibliothèque de fonctions d'exécution paresseuse, il est possible de générer une séquence infinie en définissant les membres de la séquence entière à l'aide d'équations mathématiques. Seuls les membres dont nous avons finalement besoin sont calculés à la fin.
var fibonacci2 = Lazy.generate(function() { var x = 1, y = 1; return function() { var prev = x; x = y; y += prev; return prev; }; }()); console.log(fibonacci2.length()); // Output: undefined console.log(fibonacci2.take(12).toArray()); // Output: [1, 1, 2, 3, 5,8, 13, 21, 34, 55, 89, 144] var fibonacci3 = Lazy.generate(function() { var x = 1, y = 1; return function() { var prev = x; x = y; y += prev; return prev; }; }()); console.log(fibonacci3.take(9).reverse().first(1).toArray()); //Output: [34]
第二个例子明显更有数学的味道。它依赖Lazy.js函数库。还有一些其它这样的库,比如Sloth.js、wu.js, 这些将在第三章里面讲到。
我插几句:后面这个懒执行的例子放这似乎仅仅是来秀一下函数式编程在数学正确性上的表现。 更让人奇怪的是作者还要把具有相同内部函数的懒加载写两遍,完全没意义啊…… 我觉得各位看官知道这是个懒执就行了,不必深究。
非函数式世界中的函数式编程
函数式和非函数式编程能混合在一起吗?尽管这是第七章的主题,但是在我们进一步学习之前, 还是要弄明白一些东西。
这本书并没要想要教你如何严格地用纯函数编程来实现整个应用。这样的应用在学术界之外不太适合。 相反,这本书是要教你如何在必要的命令式代码之上使用纯函数的设计策略。
例如,你需要在一段文本中找出头四个只含有字母的单词,稚嫩一些的写法会是这样:
var words = [], count = 0; text = myString.split(' '); for (i=0; count < 4, i < text.length; i++) { if (!text[i].match(/[0-9]/)) { words = words.concat(text[i]); count++; } } console.log(words);
函数式编程会写成这样:
var words = []; var words = myString.split(' ').filter(function(x){ return (! x.match(/[1-9]+/)); }).slice(0,4); console.log(words);
如果有一个函数式编程的工具库,代码可以进一步被简化:
La façon de déterminer si une fonction peut être écrite de manière plus fonctionnelle est de rechercher des boucles et des variables temporaires, telles que les variables "words" et "count" dans l'exemple précédent. Nous pouvons souvent remplacer les boucles et les variables temporaires par des fonctions d'ordre supérieur, que nous explorerons plus loin dans ce chapitre.
Javascript est-il un langage de programmation fonctionnel ?
Il reste maintenant une dernière question que nous devons nous poser : Javascript est-il un langage fonctionnel ou un langage non fonctionnel ?
Javascript est sans doute le langage de programmation fonctionnelle le plus populaire mais le moins compris au monde. Javascript est un langage de programmation fonctionnel habillé en C. Sa syntaxe est définitivement similaire à celle du C, ce qui signifie qu'elle utilise la syntaxe de bloc et l'ordre des mots infixes du C. Et elle porte le pire nom de toutes les langues vivantes. Vous n'avez pas besoin de l'imaginer pour voir combien de personnes sont confuses par la relation entre Javascript et Java, comme si son nom faisait allusion à ce que ce sera ! Mais en réalité, il a très peu de points communs avec Java. Cependant, il existe encore quelques idées pour forcer Javascript dans un langage orienté objet. Par exemple, des bibliothèques telles que Dojo et easy.js ont fait beaucoup de travail pour essayer d'abstraire Javascript afin de le rendre adapté à la programmation orientée objet. Javascript vient des années 1990, lorsque le monde réclamait à grands cris la programmation orientée objet. On nous disait que Javascript était un langage orienté objet parce que nous le souhaitions, mais en réalité ce n'était pas le cas.
Sa véritable identité remonte à ses prototypes : Scheme et Lisp, deux langages de programmation fonctionnels classiques. Javascript a toujours été un langage de programmation fonctionnel. Ses fonctions sont citoyennes de premier ordre et peuvent être imbriquées, il possède des fermetures et des fonctions composites, et il permet la rationalisation et les monades. Tous ces éléments sont essentiels à la programmation fonctionnelle. Voici quelques autres raisons pour lesquelles Javascript est un langage fonctionnel :
• Le lexique de Javascript inclut la possibilité de transmettre des fonctions en tant que paramètres, dispose d'un système d'inférence de type, prend en charge les fonctions anonymes, les fonctions d'ordre supérieur, les fermetures, etc. Ces caractéristiques sont cruciales pour la structure et le comportement qui composent la programmation fonctionnelle.
• Javascript n'est pas un langage purement orienté objet. La plupart de ses modèles de conception orientés objet sont complétés par la copie d'objets Prototype. Il s'agit d'un modèle de programmation orienté objet faible. Le script de l'European Computer Manufacturers Association (ECMAScript) - la forme officielle et l'implémentation standard de Javascript - contient la déclaration suivante dans la version 4.2.1 de la spécification :
"Javascript n'a pas de vraies classes comme C, Smalltalk et Java, mais il prend en charge les constructeurs pour créer des objets. De manière générale, dans les langages orientés objet basés sur les classes, l'état est porté par les instances, les méthodes sont portées par les classes et l'héritage est porté par des instances. uniquement pour la structure et le comportement. Dans EMACScript, l'état et les méthodes sont portés par les objets, et la structure, le comportement et l'état sont hérités »
• Javascript est un langage interprété. L'interpréteur de Javascript (parfois appelé « moteur ») est très similaire à l'interpréteur de Scheme. Ils sont tous deux dynamiques, ont tous deux des types de données flexibles faciles à composer et à transporter, évaluent tous deux le code en blocs d'expression et gèrent les fonctions de la même manière.
En d’autres termes, Javascript n’est en effet pas un langage purement fonctionnel. Il manque d’évaluation paresseuse et de données immuables intégrées. Cela est dû au fait que la plupart des interprètes sont appelés par leur nom plutôt que sur demande. Javascript n'est pas non plus très efficace pour gérer la récursivité en raison de la façon dont il gère les appels de fin. Mais tous ces problèmes peuvent être atténués par quelques considérations mineures. Une évaluation non stricte qui nécessite des séquences infinies et une évaluation paresseuse peut être réalisée via une bibliothèque appelée Lazy.js. L'immuabilité peut être obtenue simplement grâce à des compétences en programmation, mais elle n'est pas limitée par le niveau de langage mais nécessite l'autodiscipline du programmeur. L'élimination de la récursion de la queue peut être obtenue grâce à une méthode appelée trampoline. Ces problèmes seront expliqués au chapitre 6.
Il y a toujours eu de nombreux débats pour savoir si Javascript est un langage fonctionnel, un langage orienté objet, les deux, ou aucun des deux, et ces débats se poursuivront.
Enfin, la programmation fonctionnelle est une manière d'écrire du code concis grâce à des modifications, des combinaisons et une utilisation intelligentes de fonctions. Et Javascript constitue un bon moyen d’y parvenir. Si vous souhaitez vraiment exploiter tout le potentiel de Javascript, vous devez apprendre à l’utiliser comme langage fonctionnel.