Lambda la magnifique

Introduction

Ce tutoriel montre comment utiliser la classe Lambda. Il y a deux exemples ci-dessous : le premier manipule un tableau (Array) de nombres en utilisant la classe Lambda; le deuxième, une liste (List) de mots en utilisant les classes List et Lambda. Ces deux applications opèrent de manière similaire sur les données.

Fonctions Lambda

La classe Lambda nous permet d'opérer sur la totalité d'un objet itérable (Iterable) en une seule fois. Cela est souvent préférable à l'utilisation de boucles car sujet à moins d'erreurs et plus facile à lire. En l'occurrence, la classe List contient quelques unes des méthodes les plus utilisées de la classe Lambda.

La classe Lambda nous offre les possibilités suivantes :
- compte du nombre d'éléments (count)
- déterminer si l'élément Iterable est vide (empty)
- déterminer si un élément particulier est dans l'élément Iterable (has)
- déterminer si un des éléments satisfait un critère donné (exists)
- déterminer si tous les éléments satisfiont un critère donné (foreach)
- appeler une fonction pour chaque élément (iter)
- trouver les éléments qui satisfont un critère donné, et retourner un nouvel objet 'List (filter'')
- appliquer une transformation à chaque élément, et retourner un nouvel objet List (map)
- [A traduire : functional fold]. Pour plus d'information, voir wikipedia (fold) [Chercher si le sujet existe dans la WP fr]

Objets "Array", "List" et "Iterable"

La classe Lambda opère sur des objets Iterable. Les Iterables sont des typedefs, donc tout objet d'une classe qui possède une méthode iterator est par définition un Iterable. Les classes Array et List sont toutes deux Iterable, car elles définissent cette méthode. Les classes définies par l'utilisateur peuvent également être Iterable par la simple définition d'une méthode iterator appropriée au sein de la classe. Cela permet une grande flexibilité d'application pour les méthodes de la classe lambda.

Les méthodes Lambda.array et Lambda.list de la classe Lambda convertissent un Iterable en objet Array ou List, respectivement. Toutes les deux allouent de la mémoire pour une nouvelle structure de données, et copient chaque élément. Les méthodes Lambda.map et Lambda.filter retournent des objets List même si la source est un objet était un objet Array.

Le premier exemplestocke les données dans un objet Array et utilise les méthodes de la classe Lambda directement. Le second stocke les données dans un objet List et utilise les méthodes de la classe Lambda, ou, lorsque c'est possible, réalise des appels à travers l'interface List de la classe.

Count

Lambda.count retourne le nombre d'éléments d'un Iterable. Si l' Iterable est un Array ou une List, il est plus rapide d'utiliser la propriété length, mais l'exemple utilise néanmoins count pour en démontrer l'usage.

Empty

Il y a plusieurs moyens de vérifier si un Iterable est vide. Pour les objets Array et List, mieux vaut appeler la méthode isEmpty. Pour les autres Iterable, mieux vaut utiliser la méthode Lambda.empty. Il est possible de comparer la propriété length (ou le résultat de Lambda.count) à zéro, mais cela est plus lent.

Appel de méthodes de la classe Lambda

La plupart des méthodes de Lambda ont un prototype d'appel similaire : le premier argument pour toute fonction Lambda est l'Iterable sur lequel opérer. Beaucoup prennent également une fonciton en paramètre.

Observons un exemple. La méthode exists est déclarée comme :

static function exists<A>( it : Iterable<A>, f : A -> Bool ) : Bool

L'argument it est l'Iterable que Lambda va parcourir. L'argument f est une fonction qui va être appelée sur chaque élément de it. La déclaration de exists impose que f soit une focntion qui prend en entrée un seul argument du même type que it, et qui renvoie un Bool.

f peut être une fonction anonyme spécifiée au moment de l'appel lui-même (comme l'appel à exists dans les exemples ci-dessous) ou peut être une fonction nommée (comme l'appel à ''filter' dans les exemples ci-dessous)

Sortie

HaXe a des fonctions standard pour convertir des tableaux (Array) en chaînes (String). ces fonctions sont automatiquement appelées lorsque des tableaux sont affichés en utilisant neko.Lib.println. Les éléments sont concaténés, séparés par une virgule et un espace, et entourés de crochets. La fonction de conversion pour les listes (List) est similaire mais utilise des accolades au lieu de crochets.

Exemple avec des nombres


class LambdaNumberTest
{
  public static function main()
  {
    // crée et affiche un tableau de nombres
    var nums = [1, 3, 5, 6, 7, 8];
    trace("nums : " + nums);

    // compte le nombre de nombres dans le tableau, vérifie si le tabeau est vide
    trace("nombre d'éléments : " + Lambda.count(nums));
    trace("est vide : " + Lambda.empty(nums));

    // vérifie la présence d'éléemnts individuels dans le tableau
    trace("contient 2 : " + Lambda.has(nums, 2));
    trace("contient 3 : " + Lambda.has(nums, 3));

    // vérifie si un élément correspond au critère
    trace("contient un élément strictement inférieur à 10 : "
             + Lambda.exists(nums, function(ii) { return ii<10; }));
    trace("contient un élémentstrictement supérieur à 10 : "
             + Lambda.exists(nums, function(ii) { return ii>10; }));

    // vérifie si tous les éléments correspndent au critère
    trace("tous les éléments sont strictement inférieurs à 10 : "
             + Lambda.foreach(nums, function(ii) { return ii<10; }));
    trace("tous les éléments sont strictements supérieurs à 10 : "
             + Lambda.foreach(nums, function(ii) { return ii>10; }));

    // trouve les éléments pairs du tableau
    var estPair = function(num) {
        return Math.floor(num/2) == num/2;
    }
    trace("éléments pairs : " + Lambda.filter(nums, estPair));

    // multiplie chaque élément par 2
    var parDeux = function(num) {
        return num*2;
    }
    trace("multiplié par 2 : " + Lambda.map(nums, parDeux));

    // Obtient la somme de tous les éléments
    var somme = function(num, total) { return total += num; }
    trace("somme : " + Lambda.fold(nums, somme, 0));
  }
}

Compiler et exécuter avec :

haxe -neko lambda.n -main LambdaNumberTest.hx 
neko lambda.n

La sortie devrait être :

LambdaNumberTest.hx:7: nums : [1, 3, 5, 6, 7, 8]
LambdaNumberTest.hx:10: nombre d'éléments : 6
LambdaNumberTest.hx:11: est vide : false
LambdaNumberTest.hx:14: contient 2 : false
LambdaNumberTest.hx:15: contient 3 : true
LambdaNumberTest.hx:18: un élement est strictement inférieur à 10 : true
LambdaNumberTest.hx:20: un élement est strictement supérieur à 10 : false
LambdaNumberTest.hx:24: tous les élements sont strictements inférieurs à 10 : true
LambdaNumberTest.hx:26: tous les élements sont strictements supérieurs à 10 : false
LambdaNumberTest.hx:31: pairs : {6, 8}
LambdaNumberTest.hx:35: multiplié par 2: {2, 6, 10, 12, 14, 16}
LambdaNumberTest.hx:39: somme: 30

Exemple avec des mots


Note que, puisqu'il n'y a pas de moyens de créer et de remplir une liste en une seule opération, nous créons words comme un Array que nous convertissons en List avec la fonction Lambda.list.
class ListWordTest
{
  public static function main()
  {
    // crée une liste de mots
    var words = Lambda.list(['voiture', 'bateau', 'chat', 'grenouille']);
    trace("words: " + words);

    // réunit les mots en une chaîne de caractères
    trace("mots réunis : " + words.join('_'));

    // compte les mots, vérifie si la liste est vide
    trace("nombre de mots : " + Lambda.count(words));
    trace("est vide : " + words.isEmpty());

    // vérifie la présence de certains éléments dan sla liste
    trace("contient chat :" + Lambda.has(words, 'chat'));
    trace("contient arbre : " + Lambda.has(words, 'arbre'));

    // vérifie si au moins un élément correspond au critère
    trace("contient un élément qui a moins de 5 lettres : "
             + Lambda.exists(words, function(ii) { return ii.length<5; })); 
    trace("contient un élément qui a plus de 5 lettres : "
             + Lambda.exists(words, function(ii) { return ii.length>5; }));

    // vérifie si tous les éléments correspondent au critère
    trace("tous les éléments ont moins de 5 lettres : "
             + Lambda.foreach(words, function(ii) { return ii.length<5; }));
    trace("tous les éléments ont plus de 5 lettres : "
             + Lambda.foreach(words, function(ii) { return ii.length>5; }));

    // trouve les mots de 4 lettres dans la liste
    var aQuatreLettres = function(mot) { return mot.length==3; }
    trace("mots de 4 lettres : " + words.filter(aQuatreLettres));

    // met chaque mot en majusucules
    var enMajuscules = function(mot) { return mot.toUpperCase(); }
    trace("en majusucules : " + words.map(enMajuscules));

    // compte les lettres dans tous les mots
    var compteLettres = function(mot, total) { return total+=mot.length; }
    trace("nombre de lettres total : " + Lambda.fold(words, compteLettres, 0));
  }
}

Compiler et exécuter avec :

haxe -neko list.n -main ListWordTest.hx 
neko list.n

La sortie devrait être

ListWordTest.hx:7: words: {voiture, bateau, chat, grenouille}
ListWordTest.hx:10: mots réunis : voiture_bateau_chat_grenouille
ListWordTest.hx:13: nombre de mots : 4
ListWordTest.hx:14: est vide : false
ListWordTest.hx:17: contient chat : true
ListWordTest.hx:18: contient arbre : false
ListWordTest.hx:21: contient un élément de moins de 5 lettres: true
ListWordTest.hx:23: contient un élément de plus de 5 lettres : true
ListWordTest.hx:27: tous les éléments ont moins de 5 lettres : false
ListWordTest.hx:29: tous les éléments ont plus de 5 lettres : false
ListWordTest.hx:34: mots de quatre lettres : {chat}
ListWordTest.hx:38: en majuscules : {VOITURE, BATEAU, CHAT, GRENOUILLE}
ListWordTest.hx:42: nombre de lettres total : 27

Inférence de type


Nous utilisons l'inférence de type pour augmenter la lisibilité, mais nous aurions pu déclarer le type de chaque vériable explicitement.
nums aurait pû être défini comme :
    var nums : Array<Int> = [1, 3, 5, 6, 7, 8];

words aurait pû être défini comme :
    var words : Array<String> = Lambda.list(['voiture', 'bateau', 'chat', 'grenouille']);

estPair aurait pû être défini comme :
    var estPair : Int->Bool = function(num:Int) 
    { return Math.floor(num/2) == num/2; }

compteLettres aurait pû être défini comme :
    var compteLettres : String->Int->Int = function(mot:String, total:Int) 
    { return total+=mot.length; }

version #6745, modified 2009-08-13 14:27:33 by yopai