IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

JavaScript Éloquent

Une introduction moderne à la programmation
Image non disponible


précédentsommairesuivant

VI. 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 sommes presque cassé les dents, tout le monde. Bref, quand vous aurez trempé les pois, et que vous en voulez à 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 quatre 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 demie carotte, une tige de céleri et éventuellement du jambon.

Faites tremper les pois une nuit, faites-les mijoter pendant deux heures dans quatre 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 le public a 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 d'avoir des 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 de manière astucieuse des fonctions. 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 juste 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 :

 
Sélectionnez
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 :

 
Sélectionnez
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.

 
Sélectionnez
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 :

 
Sélectionnez
var paragraphes = archiveDeMails[mail].split("\n");
for (var i = 0; i < paragraphes.length; i++)
  traiterParagraphe(paragraphes[i]);

Il peut maintenant être écrit de la façon suivante :

 
Sélectionnez
forEach(archiveDeMails[mail].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éments 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 ».

Ce que fait forEach est de prendre 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 :

 
Sélectionnez
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.

 
Sélectionnez
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 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 :

 
Sélectionnez
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.

La raison pour laquelle reduce accepte cette fonction comme premier argument et non comme dernier (comme dans forEach) c'est en partie par tradition (d'autres langages ont ce fonctionnement) et en partie parce que cela permet une astuce particulière, dont on discutera plus tard à 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 tests 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.

Ex. 6.1
Sélectionnez
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 le 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.

 
Sélectionnez
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 de rejeter les valeurs de retour de la fonction, il construit un nouveau tableau pour chacune de ses valeurs.

 
Sélectionnez
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 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 lu. 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 Mark-up Language » (Langage à Balise Hyper Texte). Un document HTML est entièrement en texte. Parce qu'il doit être capable d'exprimer la structure de ce texte et de spécifier quelle donnée du texte est un titre, quelle partie du texte est en violet et ainsi de suite, quelques caractères ont un sens spécial, 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 » (NDT ou tags). 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 de document HTML :

 
Sélectionnez
<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 <tagname>, et après ils sont terminés par </tagname>. 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 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 :

 
Sélectionnez
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 ont 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 avoir lutté durement avec son livre pendant six mois, l'ermite avait seulement fini 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 :

 
Sélectionnez
% 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. Quand
la syntaxe est belle et que les règles sont claires, le
programme sera un arbre majestueux. Quand 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 paragraphes en le découpant à chaque fin de ligne ;
  2. Supprimer les caractères « % » des paragraphes d'entête et marquer ceux-ci comme entête ;
  3. Traiter le texte des paragraphes proprement dit, le 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éros(11) à 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, essayer de modifier le programme pour qu'il prenne en charge les marquages « imbriqués ».

Le manuscrit complet, sous forme de chaine, 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 chariot 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 :

 
Sélectionnez
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 entête. S'il l'est, enlever les caractères « % » et les compter. Cette fonction renvoie un objet doté de deux propriétés, contenu contenant le texte du paragraphe, et type, qui contient le tag qui devra entourer le paragraphe, "p" pour des paragraphes proprement dits, "h1" pour les entêtes avec un seul « % », et "hX" pour les entê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.

Ex. 6.2
Sélectionnez
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.

 
Sélectionnez
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, le séparer en texte normal, xte 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 exergue, 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.

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

Ex. 6.3
Sélectionnez
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 :

 
Sélectionnez
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 dotés 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 :

 
Sélectionnez
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'objet. 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 :

 
Sélectionnez
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ères 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 :

 
Sélectionnez
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 :

 
Sélectionnez
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.

Taper tous ces objets à la main serait pénible, mais nous n'allons pas faire comme ça. Une fonction utilitaire fera cela pour nous :

 
Sélectionnez
function tag(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éfinis s'ils ne s'appliquent pas, les second et troisième éléments de cette fonction peuvent être ignorés s'ils ne sont pas nécessaires.

La fonction tag 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 :

 
Sélectionnez
function lien(cible, texte) {
  return tag("a", [texte], {href: cible});
}
 
function documentHtml(titre, contenu) {
  return tag("html", [tag("head", [tag("title", [titre])]),
                      tag("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.

Ex. 6.4
Sélectionnez
function image(src) {
  return tag("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...

 
Sélectionnez
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 :

 
Sélectionnez
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...

 
Sélectionnez
print(renduHTML(lien("http://www.nedroid.com", "Des dessins !")));

Cela semble fonctionner.

 
Sélectionnez
var corps = [tag("h1", ["Le Test"]),
            tag("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 :

Ex. 6.5
Sélectionnez
function basDePage(numero) {
  return tag("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 fourni avec n'importe quelle balise supplémentaire.

 
Sélectionnez
function renduParagraphe(paragraphe) {
  return tag(paragraphe.type, map(renduFragment,
                                 paragraphe.contenu));
}
 
function renduFragment(fragment) {
  if (fragment.type == "reference")
    return basDePage(fragment.numero);
  else if (fragment.type == "enEmphase")
    return tag("em", [fragment.contenu]);
  else if (fragment.type == "normal")
    return fragment.contenu;
}

Nous y sommes presque. Les derniers éléments pour lesquels nous ne disposons pas de fonction de génération HTML sont 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ée pour les liens. Dans ce cas, la balise prend un attribut name au lieu de href.

 
Sélectionnez
function renduNoteBasDePage(noteBasDePage) {
  var ancre = tag("a", [], {name: "note" + noteBasDePage.numero});
  var numero = "[" + noteBasDePage.numero + "] ";
  return tag("p", [tag("small", [ancre, numero,
                                 noteBasDePage.contenu])]);
}

Enfin, voici une fonction qui, recevant un fichier correctement formaté et un titre de document, renvoie un document HTML.

 
Sélectionnez
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ères.

Dans les chapitres suivants, les fonctions élémentaires d'ordre supérieur comme map et reduce seront toujours disponibles et utilisées dans les 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 :

 
Sélectionnez
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à cette 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 :

 
Sélectionnez
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 autoriser la combinaison de plusieurs arguments en même temps, donc la fonction asArray est nécessaire pour constituer des tableaux normaux avec les 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).

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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.


précédentsommairesuivant
Comme ceci

Licence Creative Commons
Le contenu de cet article est rédigé par Marijn Haverbeke et est mis à disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transposé.
Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright © 2013 Developpez.com.