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.


Ex. 6.1

É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 &lt; 10 », où « &lt » veut dire « moins que ». « &gt; » est utilisé pour « > » et parce que ces codes donnent aussi à l’esperluette un sens spécial, un simple « & » est écrit « &amp; ».

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 &amp; 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 :

  1. Les paragraphes sont séparés par des lignes vides.
  2. Un paragraphe qui commence par le symbole « % » est un titre. Plus il y a de symboles « % », plus le titre est petit.
  3. À l’intérieur des paragraphes, des morceaux de texte peuvent être mis en emphase en les encadrant par des astérisques.
  4. 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 :

  1. Découper le fichier en créant un nouveau paragraphe à chaque fin de ligne.
  2. Supprimer les caractères « % » des paragraphes d’en-tête et marquer ceux-ci comme en-têtes.
  3. Traiter le texte des paragraphes proprement dits, les découper en corps de texte, textes en emphase et notes de bas de page.
  4. Déplacer les notes de bas de page en fin de document, mettre des numéros1 à leur place.
  5. Entourer chaque élément d’une balise HTML adéquate.
  6. 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.");

Ex. 6.2

É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 :

  1. Si le paragraphe commence par un astérisque, retirer la partie mise en emphase et la stocker.
  2. Si le paragraphe commence par une accolade ouvrante, retirer la note de page et la stocker.
  3. 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.
  4. S’il reste encore quelque chose dans le paragraphe, reprendre à nouveau en 1.

Ex. 6.3

É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)]);
}

Ex. 6.4

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, "&amp;"], [/"/g, "&quot;"],
                      [/</g, "&lt;"], [/>/g, "&gt;"]];
  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).


Ex. 6.5

É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.

  1. Comme ceci