Chapitre 6: Programmation fonctionnelle
Au fur et à mesure que les programmes prennent de l’ampleur, ils deviennent plus complexes et plus durs à comprendre. Nous nous considérons tous comme étant plutôt intelligents, bien sûr, mais nous ne sommes que des êtres humains et même une petite dose de chaos peut nous laisser perplexes. Et ensuite cela devient infernal. Travailler sur quelque chose que vous ne maîtrisez pas vraiment, c’est un peu comme couper des fils au hasard sur une de ces bombes à retardement que vous voyez dans les films. Si vous avez de la chance, vous couperez le bon, particulièrement si vous êtes le héros du film et que vous prenez une attitude héroïque, mais il y a toujours une possibilité de tout faire sauter.
Je vous le concède, la plupart du temps, casser un programme ne va pas causer une grosse explosion. Mais quand un programme qui a été trifouillé par quelqu’un d’ignorant dégénère en un ramassis d’erreurs, remettre de l’ordre dans le code est un travail de longue haleine, parfois il est aussi simple de recommencer depuis le début.
Ainsi, le développeur recherche toujours les moyens de faire un code aussi simple que possible. Une manière importante d’y arriver c’est de rendre le code plus abstrait. Quand on fait du code pour un programme, on se perd très facilement dans des petits détails. Vous butez sur un petit problème, vous vous penchez dessus et puis vous vous occupez du problème d’après et ainsi de suite. Au final, on lit le code à la façon d’une recette de grand-mère.
Oui, mon cher, pour faire de la soupe aux pois, vous aurez besoin de petits pois, de type sec. Et vous devez les laisser tremper pour au moins une nuit, ou vous devrez les faire cuire pendant des heures. Je me souviens une fois quand mon idiot de fils a essayé de faire de la soupe de pois. Me croirez-vous si je vous dis qu’il n’a pas fait tremper ses pois ? Nous nous y sommes tous presque cassé les dents. Bref, quand vous aurez trempé les pois, il vous en faut à peu près une tasse par personne, faites attention car ils prendront un peu de volume quand ils seront trempés, donc si vous ne prenez pas garde, ils déborderont du contenant que vous avez choisi pour ce faire, faites attention également d’utiliser beaucoup d’eau. Mais comme je vous l’ai dit, il en faut à peu près une tasse et quand ils sont trempés, vous les faites cuire avec 4 tasses d’eau pour une tasse de pois. Laissez-les mijoter pendant deux heures, ce qui sous-entend que vous mettiez un couvercle et que vous chauffiez à peine, et ensuite ajoutez des oignons coupés en dés, des tiges de céleri, peut-être une ou deux carottes et un peu de jambon. Laissez encore cuire pendant quelques minutes et après c’est prêt à être servi.
Une autre façon de décrire la recette :
Ingrédients par personne : une tasse de petits pois, un oignon coupé en morceaux, une demi carotte, une tige de céleri et éventuellement du jambon.
Faites tremper les pois une nuit, faites-les mijoter pendant deux heures dans 4 tasses d’eau (par personne), ajoutez les légumes et le jambon, faites cuire pendant dix minutes supplémentaires.
C’est plus court, mais si vous ne savez pas comment faire tremper les pois, vous raterez sûrement et les ferez tremper dans trop peu d’eau. Mais on peut rechercher comment tremper les pois, et c’est ça la clé. Si vous partez du principe que vos lecteurs ont des connaissances de base, vous pouvez recourir à un langage pour mentionner des concepts plus larges et vous exprimer d’une manière plus concise et plus claire. C’est plus ou moins ce que l’on veut dire quand on parle d’abstraction.
En quoi est-ce que cette recette tirée par les cheveux a un lien avec la
programmation ? Eh bien, évidemment, la recette est un programme. De surcroît,
la connaissance minimale que le cuisinier est supposé avoir correspond aux
fonctions et autres concepts qui sont accessibles aux codeurs. Si vous vous
rappelez de l’introduction à ce livre, des choses telles que while
rendent la
construction de boucles plus faciles. Dans le chapitre 4, nous avons écrit des
fonctions simples afin de pouvoir écrire d’autres fonctions plus courtes et
plus directes. De tels outils, dont certains sont fournis par le langage
lui-même et d’autres conçus par le programmeur, sont utilisés de manière à
réduire le nombre de détails inutiles dans le reste du programme. Ce qui rend
le programme plus abordable pour travailler dessus.
La programmation fonctionnelle, qui est le sujet qui nous intéresse dans ce chapitre, produit des abstractions en combinant des fonctions de manière astucieuse. Un codeur équipé d’un répertoire de fonctions fondamentales et, plus important, maîtrisant les manières de les utiliser, est bien plus efficace que quelqu’un qui commence à partir de zéro. Malheureusement, un environnement JavaScript de base ne fournit que peu de fonctions essentielles, donc nous devons les écrire nous-mêmes, ou, ce qui est souvent préférable, utiliser le code de quelqu’un d’autre (plus de détails dans le chapitre 9).
Il y a d’autres approches plus populaires de l’abstraction, particulièrement la programmation orientée objet, qui est le sujet du chapitre 8.
Il y a un détail fâcheux, si vous avez un peu de goût, qui doit commencer à
vous embêter, c’est la répétition incessante de boucles for
dans certaines
matrices : for (var i = 0; i < quelqueChose.length; i++) …
. Est-ce qu’on peut
en faire une abstraction ?
Le problème, c’est que si beaucoup de fonctions prennent seulement des valeurs, les combinent et donnent un résultat, une telle boucle contient un bout de code qu’elle doit exécuter. Il est facile d’écrire une fonction qui s’occupe d’une matrice et affiche chaque élément :
function printArray(tableau) { for (var i = 0; i < tableau.length; i++) print(tableau[i]); }
Mais qu’est-ce qu’on fait si on veut faire autre chose qu’afficher ? Puisque « faire quelque chose » peut être représenté par une fonction, et que les fonctions sont aussi des valeurs, on peut fournir notre action comme une valeur de type fonction :
function forEach(tableau, action) { for (var i = 0; i < tableau.length; i++) action(tableau[i]); } forEach(["Wampeter", "Foma", "Granfalloon"], print);
Et en utilisant une fonction anonyme, quelque chose comme une boucle for
peut
être écrite avec moins de détails inutiles.
function somme(nombres) { var total = 0; forEach(nombres, function (nombre) { total += nombre; }); return total; } show(somme([1, 10, 100]));
Remarquez que la variable total
est visible à l’intérieur de la fonction
anonyme, à cause des règles de portée des variables. Remarquez également que
cette version n’est pas vraiment plus courte que celle avec une boucle for
et
nécessite l’écriture peu commode });
à sa fin : l’accolade ferme le corps de
la fonction anonyme, la parenthèse ferme l’appel à la fonction forEach
et
le point virgule est nécessaire car cet appel est une instruction.
Vous obtenez une variable liée à l’élément en cours dans le tableau, nombre
,
aussi vous n’avez plus besoin d’utiliser nombres[i]
. Et quand ce tableau est
créé par l’évaluation d’une expression quelconque, il n’y a pas besoin de le
stocker dans une variable car cette expression peut être passée à forEach
directement.
Le programme sur les chats dans le chapitre 4 contient le morceau de code suivant:
var paragraphes = archiveDeMessages[message].split("\n"); for (var i = 0; i < paragraphes.length; i++) traiterParagraphe(paragraphes[i]);
Il peut maintenant être écrit de la façon suivante :
forEach(archiveDeMessages[message].split("\n"), traiterParagraphe);
Au final, une construction plus abstraite (ou « de plus haut niveau »)
correspond à plus d’informations et à moins de bruits parasites : Le code dans
la fonction somme
se lit « pour chaque nombre dans la liste des nombres,
ajouter ce nombre au total », plutôt que : « il y a une variable qui commence
à 0, et elle compte un par un jusqu’à atteindre le nombre d’élément d’un
tableau de nombres et à chaque valeur de cette variable, nous examinons
l’élément correspondant dans ce tableau et l’ajoutons au total ».
forEach
prend un algorithme, ici « parcourir un tableau » et de rendre
celui-ci abstrait. Les « trous » dans cet algorithme (ici : que faire pour
chacun des éléments du tableau), sont comblés par des fonctions passées à la
fonction algorithme.
Les fonctions qui opèrent sur d’autres fonctions sont appelées fonctions d’ordre supérieur. En opérant sur d’autres
fonctions, elles peuvent décrire des actions à un niveau supérieur. La fonction
creerFonctionAjouter
dans le chapitre 3 est aussi une fonction d’ordre
supérieur. Au lieu de prendre une valeur de fonction comme argument, elle
construit une nouvelle fonction.
Les fonctions d’ordre supérieur peuvent être utilisées pour généraliser de nombreux algorithmes que des fonctions classiques ne peuvent pas facilement décrire. Quand vous avez à votre disposition de telles fonctions, elles peuvent vous aider à concevoir votre code avec une plus grande clarté : au lieu d’une combinaison complexe de variables et de boucles, vous pouvez décomposer les algorithmes en algorithmes plus fondamentaux, qui sont appelés par leur nom et ne doivent pas être réécrits sans cesse.
Être en mesure d’écrire ce que nous voulons faire au lieu de comment nous le faisons, c’est travailler à un niveau d’abstraction supérieur. En pratique, cela implique un code plus concis, plus clair et plus agréable à lire.
Une autre catégorie utile de fonctions d’ordre supérieur modifie la fonction qui lui est fournie :
function negate(func) { return function(x) { return !func(x); }; } var isNotNaN = negate(isNaN); show(isNotNaN(NaN));
La fonction renvoyée par la fonction negate
reçoit un argument qu’elle
fournit à la fonction initiale func
et inverse son résultat. Mais si la
fonction que vous voulez inverser reçoit plus d’un argument ? Vous pouvez
accéder à n’importe quels arguments passés à une fonction à l’aide du tableau
arguments
, mais comment appeler une fonction quand vous ne savez pas combien
d’arguments vous avez ?
Les fonctions ont une méthode nommée apply
, utilisée dans les situations de
ce type. Elle prend deux arguments. Le rôle du premier argument sera détaillé
dans le chapitre 8, pour le moment nous utiliserons null
pour cet argument. Le
second argument est un tableau qui contient tous les arguments devant
s’appliquer à la fonction.
show(Math.min.apply(null, [5, 6])); function negate(func) { return function() { return !func.apply(null, arguments); }; }
Malheureusement, dans le navigateur Internet Explorer, différentes fonctions
prédéfinies comme alert
, ne sont pas vraiment des fonctions… ni quoi que ce
soit. Elles indiquent un type "object"
quand s’applique sur elles l’opérateur
typeof
et n’ont pas de méthode apply
. Vos propres fonctions n’ont pas cet
inconvénient, ce sont toujours de vraies fonctions.
Jetons un œil maintenant à quelques algorithmes plus simples qui sont reliés
aux tableaux. La fonction somme
est en fait une variante d’un algorithme qui
est habituellement appelé reduce
ou fold
:
function reduce(combiner, base, tableau) { forEach(tableau, function (element) { base = combiner(base, element); }); return base; } function ajouter(a, b) { return a + b; } function somme(nombres) { return reduce(ajouter, 0, nombres); }
reduce
convertit un tableau en une seule valeur en ayant recours de manière
répétée à une fonction qui combine un élément du tableau avec une valeur de
base. C’est exactement ce que fait la fonction somme
, donc elle peut être
raccourcie par l’utilisation de reduce
… sauf que l’addition est un opérateur
et non une fonction dans JavaScript, donc on doit d’abord la mettre dans une
fonction.
Il y a plusieurs raisons pour lesquelles reduce
accepte cette fonction comme
premier argument et non comme dernier (contrairement à forEach
). C'est d’une
part par tradition (d’autres langages ont ce fonctionnement) et d’autre part
pour permettre une astuce particulière, dont nous discuterons à la fin de ce
chapitre. Cela veut dire que lorsque l’on appelle reduce
, écrire la fonction
de réduction comme une fonction anonyme semble un peu bizarre. Car maintenant
les arguments viennent après la fonction et on perd totalement la ressemblance
avec un bloc for
normal.
Écrivez une fonction compterLesZeros
qui prend un tableau de nombres en
argument et qui renvoie le nombre de zéros qui sont rencontrés. Utilisez
reduce
.
Puis, écrivez une fonction count
de plus haut niveau qui accepte un tableau
et une fonction de test en tant qu’arguments, et qui donne en retour le nombre
d’éléments dans le tableau pour lesquels la fonction de test a renvoyé true
.
Écrivez de nouveau compterLesZeros
en utilisant cette fonction.
function compterLesZeros(tableau) { function compteur(total, element) { return total + (element === 0 ? 1 : 0); } return reduce(compteur, 0, tableau); }
La partie bizarre, celle avec le point d’interrogation et les deux
points, utilise un nouvel opérateur. Dans le chapitre 2, nous avons vu les
opérateurs unaires et binaires. Celui-ci est ternaire : il agit sur trois
valeurs. Son fonctionnement ressemble à celui de if
/else
, sauf que là où
if
exécute de manière conditionnelle des instructions, celui-ci choisit ses
expressions en fonction d’une condition. La première partie avant le point
d’interrogation est la condition. Si cette condition est true
, l’expression
après le point d’interrogation est choisie, ici 1
. Si c’est false
, la
partie après la virgule, ici 0
, est choisie.
L’utilisation de cet opérateur peut raccourcir efficacement des portions de
code. Quand les expressions à l’intérieur deviennent vraiment énormes, ou que
vous devez prendre plus de décisions à l’intérieur des portions pour les
conditions, la simple utilisation de if
et else
est habituellement plus
lisible.
Voici la solution qui utilise une fonction count
, avec une fonction qui
inclut des tests d’égalité afin d’avoir au final une fonction compterLesZeros
encore plus courte.
function count(test, tableau) { return reduce(function(total, element) { return total + (test(element) ? 1 : 0); }, 0, tableau); } function equals(x) { return function(element) {return x === element;}; } function compterLesZeros(tableau) { return count(equals(0), tableau); }
Un autre « algorithme fondamental » généralement utile en lien avec les
tableaux porte le nom de map
. Il balaye un tableau, en exécutant une
fonction sur chaque élément, tout comme forEach
. Mais au lieu d’ignorer les
valeurs de retour de la fonction, il construit un nouveau tableau contenant
chacune de ces valeurs.
function map(func, tableau) { var resultat = []; forEach(tableau, function (element) { resultat.push(func(element)); }); return resultat; } show(map(Math.round, [0.01, 2, 9.89, Math.PI]));
On remarque que le premier argument est appelé func
, pas function
. En
effet, function
est un mot-clé et n’est par conséquent pas un nom de variable
recevable.
Il était une fois un ermite vivant dans les forêts reculées des montagnes de Transylvanie. La plupart du temps, il ne faisait que se promener autour de sa montagne pour parler aux arbres et rigoler avec les oiseaux. Mais de temps en temps, quand la pluie torrentielle s’abattait sur sa petite hutte et que le vent rugissant le faisait se sentir intolérablement trop petit, l’ermite ressentait le besoin pressant d’écrire quelque chose, il voulait coucher ses pensées sur du papier, là où elles pourraient peut-être devenir beaucoup plus grandes que lui.
Après avoir échoué misérablement dans ses tentatives d’écrire de la poésie, de la fiction, de la philosophie, l’ermite décida finalement d’écrire un livre technique. Dans sa jeunesse, il avait fait de la programmation et il pensa que s’il pouvait juste écrire un bon livre sur ce sujet, la célébrité et la reconnaissance arriveraient sans doute après.
Donc il écrivit. D’abord il utilisa des morceaux d’écorce d’arbre, mais il s’avéra que ce n’était pas pratique. Il descendit au village le plus proche, et s’acheta un ordinateur portable. Après quelques chapitres, il réalisa qu’il voulait convertir son livre au format HTML, afin de le télécharger vers sa page personnelle en ligne…
Est-ce que vous connaissez le HTML ? C’est la méthode utilisée pour ajouter du formatage sur les pages des sites web et on l’utilisera de temps en temps dans ce livre, donc ce serait bien si vous saviez comment cela fonctionne, au moins de manière générale. Si vous êtes un bon étudiant, vous pourriez rechercher sur Internet une introduction au HTML maintenant et revenir quand vous l’aurez lue. La plupart d’entre vous sont sans doute des étudiants médiocres, donc je vais juste donner une petite explication et j’espère que ce sera suffisant.
HTML veut dire « HyperText Markup Language » (Langage à Balise Hyper Texte). Un document HTML est entièrement en texte. Quelques caractères ont un sens spécial pour pouvoir exprimer la structure de ce texte et spécifier quelle donnée du texte est un titre, quelle partie du texte est en violet et ainsi de suite, un peu comme les antislash (\) dans les chaînes JavaScript. Les signes « inférieur » et « supérieur » sont utilisés pour créer des « balises ». Une balise apporte de l’information supplémentaire sur le document. Elle peut fonctionner de manière autonome par exemple pour indiquer où doit apparaître une image sur la page, ou elle peut contenir du texte et d’autres balises, par exemple pour marquer le début et la fin des paragraphes.
Certaines balises sont obligatoires, un document HTML intégral doit toujours
tenir entre deux balises html
. Voici un exemple d’un document HTML :
<html> <head> <title>Une citation</title> </head> <body> <h1>Une citation</h1> <blockquote> <p>La connexion entre le langage dans lequel nous pensons/programmons et les problèmes et solutions que nous pouvons imaginer est très proche. Pour cette raison, restreindre les capacités du langage dans l’intention d’éliminer les erreurs des programmeurs est au mieux dangereuse.</p> <p>-- Bjarne Stroustrup</p> </blockquote> <p>M. Stroustrup est l’inventeur du langage de programmation C++, mais il est malgré tout une personne des plus perspicaces.</p> <p>Aussi, voici une photo d’une autruche :</p> <img src="img/autruche.png"/> </body> </html>
Des éléments qui contiennent du texte ou d’autres balises sont d’abord ouverts
avec <nomdebalise>
, puis fermés par </nomdebalise>
. L’élément html
contient toujours deux enfants : head
et body
. Le premier contient des
informations sur le document, le second contient le document en lui-même.
La plupart des noms de balise sont des abréviations cryptiques. h1
veut dire
« heading 1 » (titre 1), le plus gros titre qu’il y ait. Il y a aussi h2
jusqu’à h6
pour des titres de plus en plus petits. p
veut dire « paragraphe
», et img
veut dire « image ». L’élément img
ne contient pas de texte ou de
balise, mais il contient une information supplémentaire
(src="img/autruche.png"
) qui est appelée un « attribut ». Dans ce cas, il
contient une information sur le fichier de l’image qui devrait être affichée ici.
Parce que <
et >
ont un sens spécial dans les documents HTML, ils ne
peuvent être écrits directement dans le texte du document. Si vous voulez dire
« 5 < 10 » dans un document HTML, vous devez écrire « 5 < 10
», où « <
» veut dire « moins que ». « >
» est utilisé pour « >
» et parce que ces
codes donnent aussi à l’esperluette un sens spécial, un simple « &
» est
écrit « &
».
Maintenant, ce ne sont que les bases de l’HTML, mais elles devraient être suffisantes pour pouvoir suivre les explications dans ce chapitre, ainsi que les chapitres suivants qui traitent des documents HTML, sans trop se perdre en chemin.
La console JavaScript a une fonction viewHTML
qui peut être utilisée pour
voir des documents HTML. J’ai stocké le document de l’exemple ci-dessus dans
citationDeBjarneStroustrup
, on peut donc le voir en exécutant ce code :
viewHTML(citationDeBjarneStroustrup);
Si vous avez un genre de bloqueur de fenêtres pop-up installé ou intégré dans
votre navigateur, il interférera probablement avec viewHTML
, qui essayera de
montrer le document HTML dans une nouvelle fenêtre ou un nouvel onglet. Essayez
de configurer votre bloqueur pour autoriser les pop-ups de ce site.
Donc, pour en revenir à notre histoire, l’ermite voulait avoir son livre au
format HTML. D’abord il a juste écrit toutes les balises directement dans le
manuscrit, mais taper tous ces signes inférieur et supérieur lui a donné mal
aux doigts à la fin et il oubliait sans arrêt d’écrire &
quand il avait
besoin d’un &
. Celui lui donna mal à la tête. Ensuite il essaya d’écrire son
livre dans Microsoft Word et de le sauver en HTML. Mais le HTML qui était
produit était quinze fois plus gros et plus compliqué que ce qu’il devait être.
Et en plus Microsoft Word lui donnait mal au crâne.
La solution sur laquelle il s’arrêta était finalement celle-ci : il écrirait ce livre en texte simple, en suivant quelques règles simples pour la façon dont les paragraphes devraient être séparés et l’aspect que devraient avoir les titres. Puis il écrirait un programme pour convertir le texte en HTML précisément comme il le souhaitait.
Les règles sont celles-ci :
- Les paragraphes sont séparés par des lignes vides.
- Un paragraphe qui commence par le symbole « % » est un titre. Plus il y a de symboles « % », plus le titre est petit.
- À l’intérieur des paragraphes, des morceaux de texte peuvent être mis en emphase en les encadrant par des astérisques.
- Les notes de bas de page sont entre accolades.
Après qu’il eut lutté durement avec son livre pendant six mois, l’ermite n’avait fini que quelques paragraphes. À ce moment-là, sa cabane fut frappée par un éclair, le tuant et mettant fin à jamais à ses ambitions d’écrivain. Dans les débris carbonisés de son ordinateur portable, j’ai pu récupérer le fichier suivant :
% Le livre de la programmation %% Les deux points de vue Sous la surface de la machine, le programme évolue. Sans effort, il prend de l’ampleur et se contracte. Avec beaucoup d’harmonie, les électrons se dispersent et se regroupent. Les formes sur le moniteur ne sont que l’écume de la vague. Quand les créateurs ont construit la machine, ils y ont mis un processeur et de la mémoire. À partir de là surgissent les deux points de vue sur le programme. Du côté du processeur, l’élément actif est appelé Contrôle. Du côté de la mémoire, l’élément passif est appelé Données. Les données sont faites de simples bits, et pourtant elles prennent des formes complexes. Le contrôle consiste en de simples instructions et pourtant il exécute des tâches difficiles, de la plus petite et la plus triviale, à la plus grande et la plus compliquée. Le programme source est la donnée. Le Contrôle y naît. Le Contrôle va ensuite s’employer à créer de nouvelles données. L’un naît de l’autre, l’un ne sert à rien sans l’existence de l’autre. C’est le cycle harmonieux des Données et du Contrôle. Par nature, les Données et le Contrôle sont sans structure. Les programmeurs de la vieille école mijotaient leurs programmes à partir de cette soupe primitive. Le temps passant, les Données amorphes se sont cristallisées en de nouveaux types de données et le Contrôle chaotique a été restreint aux structures de contrôle et aux fonctions. %% Petits proverbes Quand un étudiant a questionné Fu-Tzu sur la nature du cycle des Données et du Contrôle, Fu-Tzu répondit « Pensez à un compilateur en train d’essayer de se compiler. » Un étudiant demanda : « Les programmeurs de la vieille école utilisaient des machines simples et pas de langages de programmation et pourtant ils concevaient de beaux programmes. Pourquoi utilisons-nous des machines compliquées et des langages de programmation ? » Fu-Tzu répondit : « Les bâtisseurs d’autrefois utilisaient seulement des bâtons et de l’argile et pourtant ils faisaient des cabanes magnifiques. » Un ermite passa dix ans à écrire un programme. « Mon programme peut calculer le mouvement des étoiles sur un ordinateur 286 qui fait tourner MS-DOS » annonça-t-il fièrement. « Personne ne possède un ordinateur 286 ou ne l’utilise aujourd’hui » répondit-il. Fu-Tzu avait écrit un petit programme qui était plein de variables globales et de raccourcis douteux. En le lisant, un étudiant demanda « Vous nous avez mis en garde contre ces techniques, et pourtant je les ai trouvées dans ce programme. Comment cela se fait-il ? » Fu-Tzu répondit : « Il n’y a pas besoin d’aller chercher un tuyau d’arrosage quand la maison n’est pas en feu. » {Cela ne doit pas se lire comme un encouragement à faire du code de mauvaise qualité, mais comme un avertissement contre une adhésion servile à la règle d’or.} %% Sagesse Un étudiant se plaignait des valeurs numériques. « Quand je prends la racine de deux et que je veux de nouveau son carré, le résultat est inexact ! ».] En entendant cela, Fu-Tzu rit. « Voici une feuille de papier.] Écrivez-moi la valeur précise de la racine de deux. » Fu-Tzu dit : « Quand vous sciez du bois contre le fil, beaucoup d’huile de coude est nécessaire. Quand vous programmez contre le sens, beaucoup de code est nécessaire. » Tzu-li et Tzu-ssu se vantaient de la taille de leurs programmes.] « Deux cent mille lignes », dit Tzu-li, « sans compter les commentaires ! ». « Psah », dit Tzu-ssu, « le mien fait presque un *million* de lignes déjà. » Fu-tzu dit « Mon meilleur programme fait cinq cents lignes. » En entendant cela, Tzu-li et Tzu-ssu furent éclairés. Un étudiant était resté assis immobile derrière son ordinateur pendant des heures, en ruminant furieusement. Il était en train d’essayer de concevoir une solution élégante en réponse à un problème difficile, mais il ne pouvait pas trouver le bon moyen de le faire. Fu-tzu le frappa sur l’arrière de la tête, et cria « tape quelque chose ! » L’étudiant se mit à écrire un code dégueulasse. Quand il eut terminé, il comprit tout à coup quelle était la solution simple. %% Progression Un programmeur débutant écrit un programme à la manière d’une fourmi qui construit sa fourmilière, sans même penser à la structure finale. Ses programmes seront comme des grains de sable fin. Ils peuvent tenir un moment, mais en devenant plus gros ils tombent {en référence aux dangers d’une incompatibilité interne et aux structures dupliquées dans un code en désordre.}. En prenant conscience de ce problème, le codeur commencera à passer plus de temps à réfléchir à la structure. Ses programmes seront structurés rigidement, à la manière de sculptures de pierre. Ils sont solides, mais quand ils doivent changer, on doit leur faire violence {en référence au fait que la structure a tendance à brider l’évolution du programme}. Le programmeur expérimenté sait quand la structure est importante, et quand il doit laisser les choses telles quelles.] Ses programmes sont comme de l’argile, à la fois solide et malléable. %% Langage Quand un langage de programmation est créé, on lui donne une syntaxe et des règles sémantiques. La syntaxe décrit la forme du programme, la sémantique décrit la fonction. Si la syntaxe est belle et que les règles sont claires, le programme sera un arbre majestueux. Si la syntaxe est maladroite et que les règles sont confuses, le programme sera comme un tas de ronces. On demanda à Tzu-ssu d’écrire un programme dans un langage appelé Java qui adopte une approche vraiment primitive avec les fonctions. Tous les matins, au moment où il s’asseyait en face de son ordinateur, il commençait à se plaindre. Toute la journée il jurait, accusant le langage pour tout ce qui se passait mal. Fu-tzu écouta pendant un moment, puis lui fit des reproches en lui disant « Chaque langage a sa philosophie. Suis son dessein, n’essaye pas de coder comme si tu utilisais un autre langage de programmation.»
Afin d’honorer la mémoire de notre vénérable ermite, j’aimerais finir son programme de génération HTML pour lui. Une bonne approche à ce problème ressemble à ce qui suit :
- Découper le fichier en créant un nouveau paragraphe à chaque fin de ligne.
- Supprimer les caractères « % » des paragraphes d’en-tête et marquer ceux-ci comme en-têtes.
- Traiter le texte des paragraphes proprement dits, les découper en corps de texte, textes en emphase et notes de bas de page.
- Déplacer les notes de bas de page en fin de document, mettre des numéros1 à leur place.
- Entourer chaque élément d’une balise HTML adéquate.
- Regrouper le tout en un unique document HTML.
Cette approche ne permet pas les notes de bas de page à l’intérieur des textes en emphase et inversement. C’est un choix arbitraire mais il permet de rester sur un exemple assez simple. Si, à la fin du chapitre, vous voulez vous lancer un défi, essayez de modifier le programme pour qu’il prenne en charge les marquages « imbriqués ».
Le manuscrit complet, sous forme de chaîne, est disponible sur cette page en
appelant la fonction fichierDeErmite
.
La première étape de cet algorithme est triviale. Une ligne blanche est le
résultat de deux retours-chariots consécutifs et, si vous vous rappelez que les
chaînes disposent d’une méthode split
, comme vu dans chapitre 4, vous
comprendrez que cela fera l’affaire :
var paragraphes = fichierDeErmite().split("\n\n"); print("Trouvé ", paragraphes.length, " paragraphes.");
Écrire une fonction transformeParagraphe
qui, recevant un paragraphe sous
forme de chaîne en argument, détermine si ce paragraphe est un en-tête. S’il
l’est, enlever les caractères « % » et les compter. Cette fonction renvoie un
objet doté de 2 propriétés, contenu
contenant le texte du paragraphe, et
type
, qui contient la balise qui devra entourer le paragraphe, "p"
pour des
paragraphes proprement dit, "h1"
pour les en-têtes avec un seul « % », et
"hX"
pour les en-têtes avec X
caractères « % ».
Rappelez-vous que les chaînes possèdent une méthode charAt
permettant de
rechercher un caractère précis dans les caractères qui la composent.
function transformeParagraphe(paragraphe) { var entete = 0; while (paragraphe.charAt(0) == "%") { paragraphe = paragraphe.slice(1); entete++; } return {type: (entete == 0 ? "p" : "h" + entete), contenu: paragraphe}; } show(transformeParagraphe(paragraphes[0]));
C’est là que nous pouvons essayer la fonction map
citée précédemment.
var paragraphes = map(transformeParagraphe, fichierDeErmite().split("\n\n"));
Et boum, nous avons un tableau de paragraphes proprement triés. Nous sommes allés un peu vite, nous avons oublié l’étape 3 de l’algorithme :
Traiter le texte des paragraphes proprement dit, séparer en texte normal, texte en emphase, notes de bas de page.
Ce qui se décompose en :
- Si le paragraphe commence par un astérisque, retirer la partie mise en emphase et la stocker.
- Si le paragraphe commence par une accolade ouvrante, retirer la note de page et la stocker.
- Dans les autres cas, retirer le morceau de texte jusqu’à la première mise en emphase, mise en bas de page, sinon jusqu’à la fin de la chaîne, et l’enregistrer comme texte normal.
- S’il reste encore quelque chose dans le paragraphe, reprendre à nouveau en 1.
Écrire une fonction decoupeParagraphe
qui, recevant une chaîne de caractères
représentant un paragraphe, renvoie un tableau de morceaux du texte. Réfléchir
à la façon de bien représenter les morceaux du texte.
La méthode indexOf
, qui recherche un caractère ou une sous-chaîne de
caractères dans une chaîne de caractères et renvoie sa position, ou -1
si
elle ne trouve pas, sera utile ici.
C’est un algorithme astucieux, et il y a différentes façons approximatives ou trop longues pour l’expliquer. En cas de difficulté, n’y passer qu’une minute. Essayer d’écrire des sous-fonctions qui effectuent une partie des actions qui composent l’algorithme.
Voici une solution possible :
function decoupeParagraphe(texte) { function indexOuFin(caractere) { var index = texte.indexOf(caractere); return index == -1 ? texte.length : index; } function extraitTexteNormal() { var fin = reduce(Math.min, texte.length, map(indexOuFin, ["*", "{"])); var extraction = texte.slice(0, fin); texte = texte.slice(fin); return extraction; } function extraitJusquA(caractere) { var fin = texte.indexOf(caractere, 1); if (fin == -1) throw new Error("Manque '" + caractere + "' fermant"); var extraction = texte.slice(1, fin); texte = texte.slice(fin + 1); return extraction; } var fragments = []; while (texte != "") { if (texte.charAt(0) == "*") fragments.push({type: "enEmphase", contenu: extraitJusquA("*")}); else if (texte.charAt(0) == "{") fragments.push({type: "noteBasDePage", contenu: extraitJusquA("}")}); else fragments.push({type: "normal", contenu: extraitTexteNormal()}); } return fragments; }
Remarquez l’utilisation abrupte de map
et reduce
dans la fonction
extraitTexteNormal
. Ceci est un chapitre sur la programmation fonctionnelle,
donc c’est de la programmation fonctionnelle que nous ferons ! Voyez-vous
comment cela fonctionne ? La fonction map
renvoie un tableau des positions où
les caractères indiqués ont été trouvés, ou bien renvoie la fin de la chaîne si
aucun n’a été trouvé, et la fonction reduce
prend le minimum de ces
positions, qui est la prochaine position dans la chaîne où nous allons
regarder.
Si vous voulez écrire cela sans map
et reduce
, vous obtiendrez à peu près
ceci :
var prochainAsterisque = texte.indexOf("*"); var prochaineAccolade = texte.indexOf("{"); var fin = texte.length; if (prochainAsterisque != -1) fin = prochainAsterisque; if (prochaineAccolade != -1 && prochaineAccolade < fin) fin = prochaineAccolade;
Ce qui est encore plus moche. La plupart du temps, quand il faut prendre une
décision basée sur plusieurs critères, ne serait-ce que deux, l’écrire sous
forme d’une opération dans un tableau est plus lisible que de décrire chaque
critère dans une instruction if
. (Heureusement, dans le chapitre 10, il y a une
description simple de la façon de déterminer la première occurrence de "ceci ou
cela" dans une chaîne).
Si vous avez écrit une fonction decoupeParagraphe
qui enregistre les morceaux
de texte d’une façon différente de la solution ci-dessus, vous devriez la
modifier, car les fonctions du reste de ce chapitre supposent que les morceaux
de texte sont des objets ayant des propriétés type
et contenu
.
Nous pouvons maintenant faire le lien avec transformeParagraphe
pour découper
également le texte à l’intérieur des paragraphes, ma version peut être modifiée
de la façon suivante :
function transformeParagraphe(paragraphe) { var entete = 0; while (paragraphe.charAt(0) == "%") { paragraphe = paragraphe.slice(1); entete++; } return {type: (entete == 0 ? "p" : "h" + entete), contenu: decoupeParagraphe(paragraphe)}; }
L’exécution de cette fonction sur le tableau des objets paragraphe nous renvoie un nouveau tableau d’objets paragraphe, qui contiennent des tableaux d’objets. Chacun de ces objets contient une fraction de paragraphe. La chose à faire ensuite est d’extraire les notes de bas de page, et mettre des références vers celles-ci à leur place. Comme ceci :
function extraitNotesBasDePage(paragraphes) { var notesBasDePage = []; var noteEnCours = 0; function remplaceNoteBasDePage(fragment) { if (fragment.type == "noteBasDePage") { noteEnCours++; notesBasDePage.push(fragment); fragment.numero = noteEnCours; return {type: "reference", numero: noteEnCours}; } else { return fragment; } } forEach(paragraphes, function(paragraphe) { paragraphe.contenu = map(remplaceNoteBasDePage, paragraphe.contenu); }); return notesBasDePage; }
La fonction remplaceNoteBasDePage
est appelée sur chaque morceau. Quand elle
reçoit un morceau qui doit rester où il est, elle ne fait que renvoyer ce
morceau, mais si elle reçoit une note de bas de page, elle stocke celui-ci dans
le tableau notesBasDePage
, et renvoie une référence vers celui-ci à la place.
Dans le même temps, chaque note de bas de page et sa référence sont numérotées.
Nous avons suffisamment d’outils pour extraire les informations du fichier. Il nous reste à générer un fichier HTML correct.
De nombreuses personnes pensent que la concaténation de chaînes de caractère est un bon moyen de construire du HTML. Quand elles veulent, par exemple, un lien vers un site où l’on peut jouer au jeu de Go, elles font ceci :
var url = "http://www.gokgs.com/"; var texte = "Jouez au Go !"; var texteLien = "<a href=\"" + url + "\">" + texte + "</a>"; print(texteLien);
(Où a
est la balise utilisée pour créer des liens dans les documents HTML…)
Ceci est non seulement maladroit, mais, quand la chaîne texte
se trouve
contenir un chevron (caractère "<" ou ">") ou une esperluette (caractère "&"),
cela provoque une erreur. Des choses bizarres vont se passer sur votre site
web, et vous passerez pour un amateur. Nous ne voulons pas que cela se
produise. Il est facile d’écrire quelques fonctions simples de génération de
HTML. Alors, écrivons-les.
Le secret d’une génération HTML réussie est de traiter votre document comme une structure de données plutôt qu’un simple texte plat. Les objets en JavaScript permettent de modéliser cela facilement :
var objetLien= {nom: "a", attributs: {href: "http://www.gokgs.com/"}, contenu: ["Jouez au Go !"]};
Chaque élément HTML possède une propriété nom
, contenant le nom de la balise
qu’il représente. Quand il a des attributs, il possède également une propriété
attributs
, qui est un objet contenant ces attributs. Quand il a un contenu,
il possède une propriété contenu
contenant un tableau des autres éléments
qu’il englobe. Des chaînes de caractères contiennent les portions de texte de
notre document HTML, ainsi, le tableau ["Jouer au Go!"]
signifie que ce lien
n’a qu’un élément englobé, cet élément étant un simple morceau de texte.
Saisir tous ces objets à la main serait pénible, mais nous n’allons pas faire comme ça. Une fonction utilitaire fera cela pour nous :
function balise(nom, contenu, attributs) { return {nom: nom, attributs: attributs, contenu: contenu}; }
Remarquez que du fait que nous autorisons que les propriétés attributs
et
contenu
d’un élément soient indéfinies s’ils ne s’appliquent pas, le second
et troisième élément de cette fonction peuvent être ignorés s’ils ne sont pas
nécessaires.
La fonction balise
est cependant assez simpliste, c’est pourquoi nous
écrivons quelques fonctions utilitaires pour des éléments fréquemment utilisés,
comme les liens, ou la structure générale d’un document simple :
function lien(cible, texte) { return balise("a", [texte], {href: cible}); } function documentHtml(titre, contenu) { return balise("html", [balise("head", [balise("title", [titre])]), balise("body", contenu)]); }
En reprenant, si nécessaire, l’exemple de document HTML donné précédemment,
écrire une fonction image
qui, recevant un fichier d’image, crée un élément
HTML img
.
function image(src) { return balise("img", [], {src: src}); }
Quand nous aurons créé un document, il devra être mis à plat sous forme de chaîne. Mais construire cette chaîne à partir des structures de données que nous aurons construites sera facile. L’aspect important dont il faut se souvenir est de transformer les caractères spéciaux de notre document…
function escapeHTML(texte) { var remplacements = [[/&/g, "&"], [/"/g, """], [/</g, "<"], [/>/g, ">"]]; forEach(remplacements, function(remplacement) { texte = texte.replace(remplacement[0], remplacement[1]); }); return texte; }
La méthode replace
des objets chaînes crée une nouvelle chaîne dans laquelle
toutes les occurrences du motif passé en premier argument sont remplacées par
le second argument, ainsi "Borobudur".replace(/r/g, "k")
donne "Bokobuduk"
.
Ne vous souciez pas ici de la syntaxe des motifs, cela sera vu au chapitre 10. La
fonction escapeHTML
stocke les différents motifs à remplacer dans un tableau,
aussi, il suffit d’énumérer à l’aide d’une boucle chacun de ces motifs pour les
appliquer un par un.
Les guillemets sont également remplacés, car nous utiliserons cette fonction pour le texte à l’intérieur des attributs des balises HTML. Ces attributs seront encadrés par des guillemets, et par conséquent ne pourront en contenir eux-mêmes.
Appeler quatre fois la méthode replace signifie que l’ordinateur devra balayer
quatre fois la totalité de la chaîne à convertir. Ce n’est pas très efficace.
Avec plus de soin, nous pourrions écrire une version plus complexe de cette
fonction, qui ressemblerait à la fonction decoupeParagraphe
vue précédemment,
pour ne parcourir cette chaîne qu’une seule fois. Pour le moment, nous sommes
trop paresseux pour cela. De toute façon, nous verrons au chapitre 10 une bien
meilleure façon de faire tout cela.
Pour transformer un élément HTML en une chaîne, nous pouvons utiliser une fonction récursive comme celle-ci :
function renduHTML(element) { var pieces = []; function renduAttributs(attributs) { var resultat = []; if (attributs) { for (var nom in attributs) resultat.push(" " + nom + "=\"" + escapeHTML(attributs[nom]) + "\""); } return resultat.join(""); } function rendu(element) { // Element texte if (typeof element == "string") { pieces.push(escapeHTML(element)); } // Balise vide else if (!element.contenu || element.contenu.length == 0) { pieces.push("<" + element.nom + renduAttributs(element.attributs) + "/>"); } // Balise avec du contenu else { pieces.push("<" + element.nom + renduAttributs(element.attributs) + ">"); forEach(element.contenu, rendu); pieces.push("</" + element.nom + ">"); } } rendu(element); return pieces.join(""); }
Remarquez la boucle avec in
qui extrait les propriétés d’un objet JavaScript
dans le but de créer les attributs d’une balise HTML en se basant sur ces
propriétés. Remarquez également qu’à deux reprises, des tableaux sont utilisés
pour stocker des chaînes, qui sont finalement regroupées pour ne former qu’une
seule chaîne. Pourquoi n’avons-nous pas simplement commencé avec une chaîne
vide à laquelle nous aurions ajouté d’autres chaînes, à l’aide de l’opérateur
+=
?
Il se trouve que la création des chaînes, en particulier quand elles sont de grande taille, représente un certain travail. Rappelez-vous que le contenu des chaînes JavaScript est immuable. Si vous concaténez une chaîne à une autre, une nouvelle chaîne est créée, les deux premières ne changeant pas. Si nous construisons une très grande chaîne en concaténant de nombreuses chaînes, une nouvelle chaîne doit être créée à chacun des ajouts, et sera supprimée après l’ajout suivant. Si, d’un autre côté, nous stockons toutes les chaînes dans un tableau pour les rassembler à la fin, une seule grande chaîne sera créée.
Ainsi, essayons notre outil de génération de HTML…
print(renduHTML(lien("http://www.nedroid.com", "Des dessins !")));
Cela semble fonctionner.
var corps = [balise("h1", ["Le Test"]), balise("p", ["Voici un paragraphe, et une image…"]), image("img/sheep.png")]; var doc = documentHtml("Le Test", corps); viewHTML(renduHTML(doc));
Je devrais maintenant vous prévenir que cette approche n’est pas parfaite. Ce
qu’elle génère est en fait du XML, qui est proche du HTML, mais plus
structuré. Dans les cas les plus simples, cela n’engendre pas de problème.
Cependant, il existe des séquences correctes en XML, qui ne sont pas correctes
en HTML, et peuvent embrouiller un navigateur lorsqu’il va vouloir afficher
notre document. Si par exemple vous avez un jeu de balises script
vides (on
les utilise pour insérer du JavaScript dans une page) dans votre document, les
navigateurs ne vont pas s’en apercevoir et penser que tout ce qui suit est du
JavaScript (dans ce cas, le problème peut être réglé en mettant une espace
unique entre les balises ouvrante et fermante pour que la balise ne soit pas
vide, et ainsi avoir une balise fermante).
Écrivez une fonction renduFragment
, et utilisez-la pour implémenter une autre
fonction, renduParagraphe
, qui prend un objet paragraphe (en ne tenant pas
compte des notes de bas de page) et produit l’élément HTML correct (qui peut
être un paragraphe ou un en-tête, en fonction du type
de l’objet paragraphe).
Cette fonction pourrait s’avérer utile pour produire les liens vers les références de bas de page :
function basDePage(numero) { return balise("sup", [lien("#note" + numero, String(numero))]); }
Une balise sup
affiche son contenu en « exposant », ce qui signifie que ce
contenu sera plus petit et un peu plus haut sur la ligne que le reste du texte.
La cible du lien prendra une forme telle que "#note1"
. Les liens contenant un
caractère « # » font référence aux « ancres » à l’intérieur d’une page, et ici
nous les utiliserons pour renvoyer le lecteur à la fin de la page, où seront
les notes de bas de page.
La balise pour générer des parties mises en emphase est em
; un texte normal
peut être généré sans balise supplémentaire.
function renduParagraphe(paragraphe) { return balise(paragraphe.type, map(renduFragment, paragraphe.contenu)); } function renduFragment(fragment) { if (fragment.type == "reference") return basDePage(fragment.numero); else if (fragment.type == "enEmphase") return balise("em", [fragment.contenu]); else if (fragment.type == "normal") return fragment.contenu; }
Nous y sommes presque. Le dernier élément pour lequel nous ne disposons pas de
fonction de génération HTML concerne les notes de bas de page. Pour que les
liens "#note1"
fonctionnent, une ancre doit être incluse dans chaque note de
bas de page. En HTML, les ancres sont décrites à l’aide d’une balise a
,
également utilisé pour les liens. Dans ce cas, la balise prend un attribut
name
au lieu de href
.
function renduNoteBasDePage(noteBasDePage) { var numero = "[" + noteBasDePage.numero + "] "; var ancre = balise("a", [numero], {name: "note" + noteBasDePage.numero}); return balise("p", [balise("small", [ancre, noteBasDePage.contenu])]); }
Enfin, voici une fonction qui, recevant un fichier correctement formaté et un titre de document, renvoie un document HTML.
function renduFichier(fichier, titre) { var paragraphes = map(transformeParagraphe, fichier.split("\n\n")); var notesBasDePage = map(renduNoteBasDePage, extraitNotesBasDePage(paragraphes)); var corps = map(renduParagraphe, paragraphes).concat(notesBasDePage); return renduHTML(documentHtml(titre, corps)); } viewHTML(renduFichier(fichierDeErmite(), "Le livre de la programmation"));
La méthode concat
des objets de type tableau sert à concaténer un tableau
avec un autre, tout comme l’opérateur +
le fait avec les chaînes de
caractère.
Dans les chapitres suivant, les fonctions élémentaires d’ordre supérieur comme
map
et reduce
seront toujours disponibles, et utilisées dans certains
exemples. Ici et là, on leur ajoutera d’autres outils qui nous sembleront
utiles. Dans le chapitre 9, nous verrons une approche plus structurée pour
gérer ce jeu de fonctions de base.
Lorsqu’on utilise des fonctions d’ordre supérieur, il est souvent agaçant que
les opérateurs ne soient pas des fonctions en JavaScript. Nous avons eu besoin
des fonctions ajouter
ou equals
à différents endroits. Les réécrire à
chaque fois est fastidieux, n’est-ce pas ? Aussi, à partir de maintenant, nous
supposons l’existence d’un objet nommé op
, qui contient ces fonctions :
var op = { "+": function(a, b){return a + b;}, "==": function(a, b){return a == b;}, "===": function(a, b){return a === b;}, "!": function(a){return !a;} /* et ainsi de suite */ };
Nous pouvons donc écrire reduce(op["+"], 0, [1, 2, 3, 4, 5])
pour faire la
somme d’un tableau. Mais que se passe-t-il si nous avons besoin de quelque
chose comme equals
ou creerFonctionAjouter
, dans lequel un des arguments a
déjà une valeur ? Dans ce cas nous voilà revenus à l’écriture d’une nouvelle
fonction.
Dans les cas comme ceux-là, l’utilisation d’une « application partielle » est
intéressante. Vous voulez créer une nouvelle fonction qui connaît déjà un
certain nombre de ces arguments et traite des arguments supplémentaires passés
après ces arguments fixes. Vous pourrez le faire en utilisant de façon créative
la méthode apply
d’une fonction :
function asArray(quasimentUnTableau, debut) { var resultat = []; for (var i = (debut || 0); i < quasimentUnTableau.length; i++) resultat.push(quasimentUnTableau[i]); return resultat; } function partial(func) { var argumentsFixes = asArray(arguments, 1); return function(){ return func.apply(null, argumentsFixes.concat(asArray(arguments))); }; }
Nous voulons être en mesure de regrouper plusieurs arguments en un seul, pour
cela, la fonction asArray
est nécessaire afin de constituer des tableaux
normaux avec des objets arguments
. Elle copie leur contenu dans un vrai
tableau, si bien que la méthode concat
peut lui être appliquée. Elle peut
prendre aussi un deuxième argument facultatif, permettant d’ignorer le ou les
premiers arguments.
Notez également qu’il faut stocker les arguments
de la fonction externe
(partial
) dans une variable avec un autre nom, sinon la fonction interne ne
peut pas les voir (elle a sa propre variable arguments
, qui masque celle de
la fonction externe).
Maintenant, equals(10)
peut s’écrire partial(op["=="], 10)
, sans nécessiter
d’écrire, pour l’occasion, une fonction equals
. Et vous pouvez faire ceci :
show(map(partial(op["+"], 1), [0, 2, 4, 6, 8, 10]));
La raison pour laquelle map
prend son argument de fonction avant
l’organisation du tableau est qu’il est souvent utile d’appliquer partiellement
map en lui attribuant une fonction. Ce qui donne à la fonction davantage de
puissance, elle n’opère plus sur une seule valeur mais sur un tableau de
valeurs. Par exemple, si vous avez un tableau de tableaux de nombres, et que
vous voulez les mettre tous au carré, vous procédez ainsi :
function nombreAuCarre(x) {return x * x;} show(map(partial(map, nombreAuCarre), [[10, 100], [12, 16], [0, 1]]));
Une dernière astuce qui peut être utile quand vous voulez combiner des
fonctions est la composition de fonctions. Au début de ce chapitre j’ai
montré une fonction negate
, qui applique l’opérateur booléen not au
résultat de l’appel d’une fonction :
function negate(func) { return function() { return !func.apply(null, arguments); }; }
C’est un cas particulier d’une structure plus générale : appeler la fonction A, puis appliquer la fonction B au résultat. La composition est un concept usuel en mathématiques. Elle peut être utilisée dans une fonction de haut niveau de la façon suivante :
function compose(func1, func2) { return function() { return func1(func2.apply(null, arguments)); }; } var isUndefined = partial(op["==="], undefined); var isDefined = compose(op["!"], isUndefined); show(isDefined(Math.PI)); show(isDefined(Math.PIE));
Nous voilà en train de définir de nouvelles fonctions sans utiliser du tout le
mot-clé function
. Cela peut être utile si vous avez besoin de créer une
fonction simple à passer, par exemple, à map
ou reduce
. Toutefois, quand
une fonction devient plus complexe que ces exemples, il est généralement plus
rapide (sans parler du gain en efficacité) de l’écrire avec function
.