Algorithmes fonctionnels en javascript sous la forme de cours et d'exercices. Utilisation de js dans un contexte de liste, de map, list_it. Inspiré de caml. A lire à l'envers...

lundi 16 février 2009

Fonctions en javascript: re map, reduce, foreach, compose

Finalement, on remet maintenant une petite couche sur les fonctions en javascript.
On a vu que les fonctions sont des éléments de "première classe" en javascript. On peut revoir maintenant les principales fonctions utilisées en programmation fonctionnelle, et la façon de les nommer suivant les langages:
Les fonctions sont largement inspirés de l'excellent: "Eloquent Javascript"
Le but de ce chapître est de se familiariser avec la terminologie habituelle des librairies javascripts en particulier:
  • map
  • each ou forEach
  • reduce
  • compose
  • filter
  • any
  • every


La fonctionnelle map:


La fonction map, prend en argument une liste et une fonction et à chaque élément de cette liste, applique la fonction. Elle retourne une nouvelle liste on peut
donc résumer la fonction ainsi:
map : liste fn -> liste .
Un exemple:

map = function(fun ,liste){
var resultat=[] ;
for(var i=0 ; i< liste.length ; i++) resultat.push(fun(liste[i]) ;
return resultat ;
}
// application
map( function(x){ return x+1 ;} , [1,3,5,7,9]) ; //[2,4,6,7,10]

Firefox possède la fonction map en standard, comme une des propriétés de l'objet Array .

// Firefox :
var incremente = function(n){ return n+1 ;} ;
[2, 4, 6, 8].map(incremente) ; // [3,5,7,9]
// IE ... n'existe pas .

Pour doter n'importe quel navigateur de cette fonction il faut utiliser le prototype sur l'objet Array , en effet:
[1,3].constructor == Array ;
donc pour bénéficier de Array.map ; il faut écrire la fonction suivante:

Array.prototype.map = function(fun){
var resultat =[] ;
for(var i=0; i< liste.length ; i++)
resultat.push(fun(liste[i]) ;

return resultat ;
}
// Certain poussent le bouchon et rajoutent des variables optionnelles:
Array.prototype.map = function(fun , i , debut, fin , increment ){
var resultat =[] ;
var debut= debut || 0 ;
var fin= fin||liste.length ;
var increment = increment || 1 ;
for(var i=debut; i< fin ; i+= increment){
resultat.push(fun(liste[i],i) ;
}

return resultat ;
}

Les fous de sécurité rajouteront même un try/catch sur la ligne resultat.

La fonctionnelle do_list, foreach


On a vu que map renvoi un tableau, parfois ce n'est pas nécessaire. On peut écrire ainsi:

var do_list = function(fun, liste){
for(var i=0 ; i< liste.length; i ++){
fun( liste[i] ) ;
}
return ;
}
// mootools utilise cette fonction :
forEach: function(fn, bind){
for (var i = 0, l = this.length; i < l; i++) fn.call(bind, this[i], i, this);
}


La plupart des librairies javascript utilisent d'autres appellation pour do_list, foreach, each.

La fonction reduce, list_it ou it_list :


Nous avions vu la fonction de caml list_it et la fonction it_list, cette fonction applique une fonction binaire (prenant deux arguments) à tous les éléments successifs d'une liste:

function reduce(liste, base, f){
foreach(function(element){ base= f(element,base); } ,liste );
return base;
}
//Exemple:
function add(x,y){ return x+y;}
reduce([1,4,4,3] , 0 , add); //12

En général l'élement de base est l'élément neutre pour l'opération, ici 0 pour l'addition.
Mais attention ! reduce n'est pas une fonction commutative ! l'ordre d'appartition des opérateurs est important si la fonction de base n'est pas commutative (exemple la soustraction)
Autre exemple:

function lt(x,y){ return x < y && x || y ;};
reduce([4,4,3,1] , Infinity , lt);//3


Partial ou curryfication:



function partial(func) {
var fixedArgs = asArray(arguments, 1);
return function(){
return func.apply(null, fixedArgs.concat(asArray(arguments)));
};
}
// souvenez vous arguments n'est pas un vrai tableau
// et il faut le transformer en tableau
function asArray(quasiArray, start) {
var result = [];
for (var i = (start || 0); i < quasiArray.length; i++)
result.push(quasiArray[i]);
return result;
}
//exemple
add2 = partial(add,2);
add2(3); //5


Composée de fonction, "f rond g":


Pour composer la fonction (f o g)(arguments) on peut écrire:
 
function compose(f,g){
return function(){ f( g.apply(null,arguments) ); };
}
// Un exemple simple:
fog = compose( alert , add );
fog(1,3); // alert(4);

Fonction filter:


Filtrer une liste suivant un prédicat:

// exemple:
function filter(liste, predicat){
return reduce(liste,[], function(e,t){ return predicat(e)? t.push(e)&& t: t;}) ;
}
filter([1,3,4,5], function(x){ return x>3;}); // [4,5]

Fonction any


On cherche ici s'il y a un élément de la liste qui répond à un prédicat, la signature est du type liste * function -> boolean

function any(liste, predicat){
return reduce(liste, false, function(e, bool){ return predicat(e)||bool ;});
}
//exemple
any([1,3,4,5], function(x){ return x>1;}); // true;
//qui montre que notre fonction mérite un peu d'optimisation car
// même si le premier résultat est juste, elle boucle jusqu'au bout.
// La solution est d'utiliser une exception:
function any(liste, predicat){
try{
return reduce(liste,
false,
function(e, bool){
if(predicat(e)) throw "found" ; return false ;});
}catch(e){ return e=="found" ;}
}

any([1,3,4,5], function(x){ return x>1;});

L'inverse de any. every


Ici même chose en sens inverse tout doit répondre au prédicat:

function every(liste, predicat){
try{
return reduce(liste,
true,
function(e, bool){
if(!predicat(e)) throw "nfound" ; return true ;});
}catch(e){ if(e=="nfound")return false; else throw e ; }
}


De nombreuses variantes des mêmes fonctions existent, en fait 3 "fonctionnelles" permettent de reconstituer l'ensemble map, foreach et reduce.
Les différentes librairies, prototypes, jquery utilisent largement ces bibliothèques.
Enfin il existe certaines librairies qui transforme les expressions du type x -> x*x en fonction.. pas mal non ?