De Grote Lambda

Introductie

Deze tutorial laat zien hoe de Lambda class wordt toegepast. Onderaan vindt u 2 voorbeelden: het eerste voorbeeld past een Array met getallen aan m.b.v. de Lambda class en het tweede voorbeeld manipuleert een List waarbij zowel de List als de Lambda class gebruikt wordt. Beide voorbeelden bewerken de data op onderling vergelijkbare wijze.

Lambda functies

De Lambda class maakt het mogelijk om een hele Iterable in 1 keer te verwerken. Dit heeft meestal de voorkeur boven het doorlopen van een programmalus: de implementatie is minder foutgevoelig en eenvoudiger leesbaar. Om e.e.a. te vergemakkelijken, heeft de List class standaard al enkele veelgebruikte Lambda methoden aan boord.

De Lambda class maakt het volgende mogelijk:

  • tel het aantal elementen (count);
  • stel vast of een "Iterable" leeg is (empty);
  • stel vast of een "Iterable" bepaalde elementen bevat (has);
  • stel vast of er een element aan bepaalde criteria voldoet (exists);
  • stel vast of alle elementen aan bepaalde criteria voldoen (foreach);
  • voer een bepaalde functie uit voor ieder element (iter);
  • zoek alle elementen die aan bepaalde criteria voldoen en geef deze terug als nieuwe List (filter);
  • pas een conversie toe op elk element en geef deze terug als nieuwe List (map);
  • functionele fold; voor meer informatie, zie wikipedia-nl of beter nog, de engelse versie: wikipedia-en (fold).

Array's, List's en Iterable's

De Lambda class werkt met Iterable's. Iterable's zijn typedef's, dus iedere class met een iterator methode is volgens deze definitie een Iterable. De Array en List class zijn beide een Iterable omdat ze de iterator methode kennen. Een zelfgemaakte class kan net zo goed een Iterable zijn door de eenvoudige toevoeging van een iterator methode in de class source. Daarmee is het direct mogelijk om gebruik te maken van de veelzijdige flexibiliteit van de Lambda methoden.

De Lambda.array en Lambda.list functies in de Lambda class converteren een willekeurige Iterable naar respectievelijk een Array of een List. Beide functies verzorgen daarvoor de geheugen allocatie en het kopieren van elk element. De Lambda.map en Lambda.filter functies geven altijd een List als waarde terug, zelfs als de input een Array betreft...

Het eerste voorbeeld slaat de gegevens op in een Array en maakt direct gebruik van de Lambda functie's. Het tweede voorbeeld slaat de gegevens op in een List en gebruikt de Lambda class of, waar mogelijk, Lambda methoden van de eigen List class.

Count

Lambda.count geeft het aantal elementen terug in een Iterable. Als een Iterable een Array of een List is, is de code sneller als gebruik wordt gemaakt van de eigen length property van beide, maar in het voorbeeld wordt toch van count gebruik gemaakt om het gebruik te verduidelijken.

Empty

Er zijn diverse manieren om te controleren of een Iterable leeg is. Als het om een Array of List gaat, kan dit het beste door de isEmpty methode te gebruiken. Alle andere Iterable's kunnen beter gebruik maken van de Lambda.empty methode. Het is mogelijk om te testen of de length of het resultaat van Lambda.count gelijk is aan 0, maar dit is langzamer in uitvoering.

Lambda functies aanroepen

De meeste Lambda methoden worden op een vergelijkbare manier aangeroepen. Het eerste argument is steeds de Iterable waarmee gewerkt wordt. Veel methoden verwachten ook een functie als argument.

Met een voorbeeld wordt het eenvoudig duidelijk. De exists methode is gespecificeerd als:

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

Het argument it is de Iterable die Lambda zal doorwerken. Het argument f verwijst naar de functie die wordt aangeroepen voor elk element in it. De definitie voor exists legt vast dat f 1 enkel argument nodig heeft van hetzelfde type als de elementen van it en dat f een Bool teruggeeft als waarde.

f kan een anonieme functie zijn, gespecificeerd in de aanroep van de methode (zoals in het gebruik van de aanroepen naar exists in de voorbeelden hieronder) of het kan een benoemde functie zijn (zoals in de aanroepen naar filter in de voorbeelden hieronder).

Output

Haxe heeft ingebouwde functionaliteit voor conversie van een Array in een String. De bijbehorende routines worden automatisch aangeroepen als Array's worden geprint m.b.v. neko.Lib.println. De elementen worden aan elkaar gekoppeld, gescheiden door een komma en spatie en omringd met vierkante haken. De conversie routine voor List's is vergelijkbaar, maar gebruikt accoladen i.p.v. vierkante haken.

Getallen voorbeeld

import neko.Lib;

class LambdaNumberTest
{
  public static function main()
  {
    // create and output an array of numbers
    var nums = [1, 3, 5, 6, 7, 8];
    neko.Lib.println("nums: " + nums);

    // count the numbers in the array, see if the array is empty
    neko.Lib.println("count: " + Lambda.count(nums));
    neko.Lib.println("is empty: " + Lambda.empty(nums));

    // check for individual elements in the array
    neko.Lib.println("contains 2: " + Lambda.has(nums, 2));
    neko.Lib.println("contains 3: " + Lambda.has(nums, 3));

    // check if any element fits a criteria
    neko.Lib.println("contains an element less than 10: "
             + Lambda.exists(nums, function(ii) { return ii<10; }));
    neko.Lib.println("contains an element greater than 10: "
             + Lambda.exists(nums, function(ii) { return ii>10; }));

    // check if all elements fit a criteria
    neko.Lib.println("all elements less than 10: "
             + Lambda.foreach(nums, function(ii) { return ii<10; }));
    neko.Lib.println("all elements greater than 10: "
             + Lambda.foreach(nums, function(ii) { return ii>10; }));

    // find even elements from the array
    var isEven = function(num) { return Math.floor(num/2) == num/2; }
    neko.Lib.println("even: " + Lambda.filter(nums, isEven));

    // multiply each element by 2
    var timesTwo = function(num) { return num*2; }
    neko.Lib.println("times two: " + Lambda.map(nums, timesTwo));

    // get the sum of all elements
    var sum = function(num, total) { return total += num; }
    neko.Lib.println("sum: " + Lambda.fold(nums, sum, 0));
  }
}

Compileren en uitvoeren met:

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

De uitvoer zou moeten zijn:

nums: [1, 3, 5, 6, 7, 8]
count: 6
is empty: false
contains 2: false
contains 3: true
contains an element less than 10: true
contains an element greater than 10: false
all elements less than 10: true
all elements greater than 10: false
even: {6, 8}
times two: {2, 6, 10, 12, 14, 16}
sum: 30

Woorden voorbeeld

import neko.Lib;

class ListWordTest
{
  public static function main()
  {
    // create a list of words
    var words = Lambda.list(['car', 'boat', 'cat', 'frog']);
    neko.Lib.println("words: " + words);

    // join the words into a string
    neko.Lib.println("join: " + words.join('_'));

    // count the words, see if the list is empty
    neko.Lib.println("count: " + Lambda.count(words));
    neko.Lib.println("is empty: " + words.isEmpty());

    // check for individual elements in the list
    neko.Lib.println("contains cat: " + Lambda.has(words, 'cat'));
    neko.Lib.println("contains tree: " + Lambda.has(words, 'tree'));

    // check if any element fits a criteria
    neko.Lib.println("contains an element less than 5 letters: "
             + Lambda.exists(words, function(ii) { return ii.length<5; })); 
    neko.Lib.println("contains an element greater than 5 letters: "
             + Lambda.exists(words, function(ii) { return ii.length>5; }));

    // check if all elements fit a criteria
    neko.Lib.println("all elements are less than 5 letters: "
             + Lambda.foreach(words, function(ii) { return ii.length<5; }));
    neko.Lib.println("all elements are greater than 5 letters: "
             + Lambda.foreach(words, function(ii) { return ii.length>5; }));

    // find three letter elements from the list
    var isThreeLetters = function(word) { return word.length==3; }
    neko.Lib.println("three letter words: " + words.filter(isThreeLetters));

    // capitalze each word
    var capitalize = function(word) { return word.toUpperCase(); }
    neko.Lib.println("capitalized: " + words.map(capitalize));

    // count the letters in all words
    var countLetters = function(word, total) { return total+=word.length; }
    neko.Lib.println("total letters: " + Lambda.fold(words, countLetters, 0));
  }
}

Compileren en uitvoeren met:

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

De uitvoer zou moeten zijn:

words: {car, boat, cat, frog}
join: car_boat_cat_frog
count: 4
is empty: false
contains cat: true
contains tree: false
contains an element less than 5 letters: true
contains an element greater than 5 letters: false
all elements are less than 5 letters: true
all elements are greater than 5 letters: false
three letter words: {car, cat}
capitalized: {CAR, BOAT, CAT, FROG}
total letters: 14

version #5878, modified 2009-04-30 14:47:56 by mloots