BT
x Votre opinion compte ! Merci de bien vouloir répondre au sondage InfoQ concernant vos habitudes de lecture !

Démystifier les iteratees avec Java

Écrit par Slim Ouertani le 01 nov. 2013 |

Introduction

La programmation réactive est en train de faire le Ramdam. Mais souvent, quand on attaque ce domaine, on est freiné par des notions purement mathématiques et des démonstrations qui jonglent avec des terminologies fonctionnelles.

Les Iteratees présentent un exemple parfait d’un outil super-puissant néanmoins difficile à avaler par le commun des mortels.

Le but de cet article est d’expliquer les Iteratees pour les passionnés de JAVA en évitant au maximum les notions fonctionnelles très complexes.

Définition brute

Un Iteratee est une machine à états immutables qui produit son résultat d’une manière asynchrone et non bloquante.

La philosophie

Les Iteratees ressemblent beaucoup aux norias, ils manipulent des flux d'éléments et à chaque entrée, ils produisent un nouveau résultat.

Exemple

Calculer la somme de la liste d’entiers [ 2 , 7 , 5 ] avec un Iteratee SumIteratee :

  • Itération 0 : SumIteratee contient la somme 0 et n’a pas encore consommé d'éléments de la liste.

  • Itération 1 : SumIteratee consomme [2] de la liste et produit un SumIteratee[2].

  • Itération 2 : SumIteratee[2] consomme 7 de la liste et produit un SumIteratee[9].

  • Itération 3 : SumIteratee[9] consomme 5 de la liste et produit un SumIteratee[14].

  • Itération 4 : SumIteratee[14] atteint la fin de la liste et contient la somme 14.

Une définition d’un Iteratee sur cette base sera :

public interface Iteratee<E> {
    Iteratee<E> handle(E e);
}

A chaque pas d’itération, l’Iteratee traite un élément E et produit un nouveau Iteratee. Notre SumIteratee sera :

public class SumIteratee implements Iteratee<Integer>{

    final Integer sum;

    public SumIteratee(Integer sum) {
        this.sum = sum;
    }

    public SumIteratee() {
        this(0);
    }

    @Override
    public SumIteratee handle(Integer e) {
        return new SumIteratee(sum +e);
    }
    public Integer getSum() {
        return sum;
    }
}

Afin de tester notre SumIteratee on aura à pousser l’itération :

List<Integer> l = Arrays.asList(2,7,5);
SumIteratee Iteratee = new SumIteratee();
for (Integer elm : l) {
     Iteratee = Iteratee.handle(elm);
}
Integer expResult = 14;
Integer result = Iteratee.getSum();
assertEquals(expResult, result);

De la même manière, un Iteratee qui calcule la valeur maximale d’une liste est défini comme suit :

public class MaxIteratee implements Iteratee<Integer>{

    private final Integer max ;

    public MaxIteratee(Integer max) {
        this.max = max;
    }

    public MaxIteratee() {
        this.max= Integer.MIN_VALUE;
    }

    @Override
    public MaxIteratee handle(Integer e) {
        return new MaxIteratee(e> max ? e : max);
    }

    public Integer getMax() {
        return max;
    }
}

Pour le tester, on pourra de même appliquer l’itération sur notre liste :

List<Integer> l = Arrays.asList(2,7,5);
MaxIteratee Iteratee = new MaxIteratee();
for (Integer elm : l) {
     Iteratee = Iteratee.handle(elm);
}
Integer expResult = 7;
Integer result = Iteratee.getMax();
assertEquals(expResult, result);

Les Inputs

Nous avons exécuté l’itération sur une liste d’entiers, mais en réalité, on traite des streams et des flux. Pour les listes finies, on sait d’avance leurs tailles, ce qui n’est pas le cas si on traite des flux d’octets transmis sur le réseau.

Pour y remédier, nous allons introduire un conteneur d'éléments Input :

interface Input<E> {
    InputState state();
}

qui peut avoir trois états ( on peut avoir plus d’un état selon les cas )

enum InputState {
    EOF, Empty, EL
}

Avec :

  • EOF : signaler fin du flux
  • Empty : silence
  • EL : conteneur d’élément

L’interface complète sera :

public interface Input<E> {

    enum InputState {

        EOF, Empty, EL
    }

    InputState state();

    Input EOF = new Input() {

        @Override
        public InputState state() {
            return InputState.EOF;
        }
    };

    Input EMPTY = new Input() {

        @Override
        public InputState state() {
            return InputState.Empty;
        }
    };

    static <E> Input<E> el(E e ) {
        return new El(e);
    };

    class El<E> implements Input<E> {

        final E e;

        public El(E e) {
            this.e = e;
        }

        @Override
        public InputState state() {
            return InputState.EL;
        }

        public E getE() {
            return e;
        }

        @Override
        public String toString() {
            return "El{" + "e=" + e + '}';
        }
    };
}

Il est à noter que toutes les implémentations de notre input sont immutables et que EMPTY et EOF sont créés une seule fois comme ils peuvent être partagés. Entre temps, el(E e) est une factory pour simplifier la construction des inputs.

Cette facilité de définition des méthodes statiques au sein de l’interface Input est faite grâce aux nouveautés de Java 8 (utilisé dans la suite de cet article, mais je vais m’attarder sur l’utilisation des expressions lambda comme promis ).

Notre Iteratee devient :

public interface Iteratee<E> {
    Iteratee<E> handle(Input<E> e); 
}

Et notre SumIteratee est redéfini de cette manière :

public SumIteratee handle(Input<Integer> e) {
        switch (e.state()) {
            case EL:
                Input.El<Integer> el = (Input.El) e;
                Integer elem = el.getE();

                return new SumIteratee(sum + elem);
            default:
                return this;
        }
}

Pour tester, on a enrichi notre liste avec des EMPTY et des EOF :

List<Input<Integer>> l = Arrays.asList(Input.el(2),Input.EMPTY,Input.el(7),Input.el(5), Input.EOF, Input.el(100));
SumIteratee Iteratee = new SumIteratee();
Iterator<Input<Integer>> iterator = l.iterator();
boolean stop =false;
while(iterator.hasNext()&& !stop) {
    Input<Integer> elm = iterator.next();
    switch(elm.state()){
        case EL : Iteratee = Iteratee.handle(elm); break;
        case Empty : break;
        default : stop =true; 
    }
}

Integer expResult = 14;
Integer result = Iteratee.getSum();
assertEquals(expResult, result); 

Accumulator

Dans les exemples précédents, SumIteratee et MaxIteratee traitent et produisent des entiers. Que faire si, par exemple, on veut traiter un flux de String et on voudrait générer un type autre que String ? Par exemple, une liste de String qui contient tout les éléments que l’Iteratee a déjà pu intercepter ?

Il est essentiel de faire adapter l’interface de notre Iteratee afin de supporter un deuxième type : le type de la valeur accumulée.

public interface Iteratee<E,A> {
    Iteratee<E,? extends A> handle(Input<E> e); 
}

A titre d’exemple, StringIteratee est un Iteratee qui accumule les chaînes de caractères :

public class StringIteratee implements Iteratee <String,String[]> {

    final String[] acc;

    public StringIteratee(String[] acc) {
        this.acc = acc;
    }

    public StringIteratee() {
        this(new String[0]);
    }

    @Override
    public StringIteratee handle(Input<String> e) {
         switch (e.state()) {
            case EL:
                Input.El<String> el = (Input.El) e;
                String elem = el.getE();
                 String[] newAcc = new String[acc.length+1];
                 System.arraycopy(acc, 0, newAcc, 0, acc.length);
                 newAcc[acc.length]= elem;
                return new StringIteratee(newAcc);
            default:
                return this;
        }
    }

    public String[] getAcc() {
        return acc;
    }
}

Tester StringIteratee

List<Input<String>> l = Arrays.asList(Input.el("a"),Input.EMPTY,Input.el("b"),Input.el("c"), Input.EOF, Input.el("ZZZ"));
StringIteratee Iteratee = new StringIteratee();
Iterator<Input<String>> iterator = l.iterator();
boolean stop =false;
while(iterator.hasNext()&& !stop) {
    Input<String> elm = iterator.next();
     switch(elm.state()){
        case EL : Iteratee = Iteratee.handle(elm); break;
        case Empty : break;
        default : stop =true; 
    }
}

String[] expResult = {"a","b","c"};
String[] result = Iteratee.getAcc();
assertArrayEquals(expResult, result);

Les états

Tout au long des précédents tests, nous avions à traiter les éléments jusqu’à détecter EOF. Mais, y a-t-il moyen d’informer l’itération suivante de son état pour s’arrêter ou bien alerter qu’une exception est survenue en cours du parcours ?

Il est à noter que les Iteratees sont des machines à état : à chaque étape, ils changent de statut.

public interface Iteratee<E, A> {    
  …..

    enum StepState {
        Done, CONT, ERROR
    }

    default StepState onState() {
        return StepState.CONT;
    }
….
}

Désormais, il sera possible d’implémenter des méthodes non statiques dans les interfaces à partir de java 8 : c’est fini les casses-têtes qui existaient depuis plus de 15 ans et les histoires d’héritage multiple !

default StepState onState() 

onstate, est l’implémentation qui sera invoquée par défaut à condition que les classes filles ne la redéfinissent pas.

Vous avez remarqué que tout au long des tests précédents, on a remplacé à chaque étape notre Iteratee avec :

iteratee = Iteratee.handle(elm);

Manipuler des objets mutables rompt les notions de la programmation fonctionnelle d'où hérite le concept des Iteratees.

Les fonctions

Une fonction est simplement une interface qui ressemble à :

public interface Function<T, R> {
       R apply(T t);
}

Java 8 embarquera une panoplie de classes pour des besoins fonctionnels. Cette richesse nous permet de répondre à notre besoin et supprimer la mutabilité d’usage des iteratees.

L’Iteratee va consommer l’Iteratee de l’étape précédente et selon l’état, il devra décider.

Ainsi l’interface de l’Iteratee sera :

public interface Iteratee<E, A> {

   <B> B handle(Function<Iteratee<E, A>, B> folder);        

    enum StepState {
        Done, CONT, ERROR
    }

    default StepState onState() {
        return StepState.CONT;
    }
}

Mais, d'où vient <B> ? Pour l’instant, penser que B sera utilisée pour des besoins de chaînage et qu’on n’a pas à s'inquiéter vu qu’elle sera la valeur de retour de la méthode apply de folder.

Cette méthode prend en paramètre un Iteratee dans un statut particulier et s’il se place dans un état Cont (apte à consommer encore plus d'éléments), elle lui injecte l'élément suivant.

Pour simplifier la construction des Iteratees on propose trois implémentations relatives à chaque état :

Done

Classe immutable qui contient la valeur finale de l’itération "a". S’il reste des données non encore traitées "input", la méthode handle construit un nouveau Iteratee avec un état Done et l’applique à la méthode apply. Encore une fois, on n’a pas à se soucier du <B>.

class Done<E, A> implements Iteratee<E, A> {

    final A a;
    final Input<E> input;

    @Override
    public StepState onState() {
        return StepState.Done;
    }

    public A getA() {
        return a;
    }

    public Input<E> getInput() {
        return input;
    }

    public Done(A a, Input<E> e) {
        this.a = a;
        this.input = e;
    }

    public Done(A a) {
        this(a, Input.EMPTY);
    }

    @Override
    public <B> B handle(Function<Iteratee<E, A>, B> folder) {
        Iteratee<E, A> done = new Iteratee.Done(a, input);
        return folder.apply(done);
    }
}

Error

L’Iteratee en état d’exception :

class Error<E> implements Iteratee<E, Object> {

    final String msg;
    final Input<E> input;

    public Error(String msg, Input<E> input) {
        this.msg = msg;
        this.input = input;
    }

    @Override
    public StepState onState() {
        return StepState.ERROR;
    }

    public String getMsg() {
        return msg;
    }

    public Input<E> getInput() {
        return input;
    }

    @Override
    public <B> B handle(Function<Iteratee<E, Object>, B> folder) {
        Iteratee<E, Object> s = new Iteratee.Error(msg, input);
        return folder.apply(s);
    }

    @Override
    public String toString() {
        return "Error{" + "msg=" + msg + ", input=" + input + '}';
    }
}

Cont

Elle nécessite plus d'intérêt. A chaque Input, elle génère un nouveau Iteratee. Ce qui se traduit par la fonction :

Function<Input<E>, Iteratee<E, A>> k;

D’ou :

class Cont<E, A> implements Iteratee<E, A> {

    Function<Input<E>, Iteratee<E, A>> k;

    public Cont(Function<Input<E>, Iteratee<E, A>> k) {
        this.k = k;
    }

    public Function<Input<E>, Iteratee<E, A>> getK() {
        return k;
    }

    @Override
    public StepState onState() {
        return StepState.CONT;
    }

    @Override
    public <B> B handle(Function<Iteratee<E, A>, B> folder) {
        Iteratee<E, A> s = new Iteratee.Cont(k);
        return folder.apply(s);
    }
}

Ces classes sont les bases de notre Iteratee. On va essayer de simplifier leurs créations avec des factories.

static <E> Error<E> Error(String msg, Input<E> input) {
    return new Error(msg, input);
}

static <E, A> Cont<E, A> Cont(Function<Input<E>, Iteratee<E, A>> k) {
    return new Cont(k);
}

static <E, A> Done<E, A> Done(A a, Input<E> e) {
    return new Done(a, e);
}

static <E, A> Done<E, A> Done(A a) {
    return new Done(a);
}

Notre StringIteratee devient :

public Iteratee<String, String[]> buildIteratee(Input<String> e) {
    switch (e.state()) {
        case EL:
            Input.El<String> el = (Input.El) e;
            String elem = el.getE();
            String[] newAcc = new String[acc.length + 1];
            System.arraycopy(acc, 0, newAcc, 0, acc.length);
            newAcc[acc.length] = elem;
            return new StringIteratee(newAcc);
        case EOF:
            return new Iteratee.Done(acc);
        case Empty:
            return this;
        default:
            throw new IllegalStateException();
    }
}

@Override
public <B> B handle(Function<Iteratee<String, String[]>, B> folder) {
    Function<Input<String>, Iteratee<String, String[]>> handler = new Function<Input<String>, Iteratee<String, String[]>>() {

        @Override
        public Iteratee<String, String[]> apply(Input<String> e) {
           return buildIteratee(e);
        }

    };
    Iteratee.Cont<String, String[]> stCont = Iteratee.Cont(handler);
    return folder.apply(stCont);
}

Le même principe à appliquer pour les autres Iteratees.

Pour ceux qui ne font pas d'allergie aux “->” notre handle sera simplifié avec les expressions lambda en :

@Override
public <B> B handle(Function<Iteratee<String, String[]>, B> folder) {
    Function<Input<String>, Iteratee<String, String[]>> handler = (Input<String> e) -> buildIteratee(e);
    Iteratee.Cont<String, String[]> stCont = Iteratee.Cont(handler);
    return folder.apply(stCont);
}

En une seule ligne :

@Override
public <B> B handle(Function<Iteratee<String, String[]>, B> folder) {
    return folder.apply(Iteratee.Cont((Input<String> e) -> buildIteratee(e)));
}

Si on se réfère à la définition brute d’un Iteratee, il nous reste encore un dernier point à vérifier : asynchrone et non bloquante.

Future et CompletableFuture

Si on revient à la déclaration de l’interface Iteratee, la méthode handle est bloquante :

<B> B handle(Function<Iteratee<E, A>, B> folder); 

Première essai: Utiliser les Futures java

La méthode handle va produire un Future de B au lieu de B.

<B> Future<B> handle(Function<Iteratee<E, A>, B> folder); 

Mais cette solution manque d'homogénéité : on a avancé qu’on n’a pas à s'inquiéter de la génération de B et qu’elle sera utilisée telle qu’elle comme valeur de retour.

Deuxième essai : Future<B>

Une proposition plus homogène :

<B> Future<B> handle(Function<Iteratee<E, A>, Future<B>> folder); 

Troisième essai : CompletableFuture<B>

Le problème avec les Futures c’est qu’ils ne sont pas composables : on ne peut pas chaîner les Futures en java. L'idée sera d’utiliser une structure monadique des Futures qui soit composable et qui permet de travailler en toute simplicité. Désormais, Java 8 embarque CompletableFuture qui implémente Future et qui présente une panoplie de méthodes.

Ci dessous la version finale de notre méthode handle :

<B> CompletableFuture<B> handle(Function<Iteratee<E, A>, CompletableFuture<B>> step);

Tout le code source de l’Iteratee :

https://github.com/ouertani/ouertanitee/blob/master/src/main/java/com/technozor/ouertanitee/Iteratee.java

Voila on a fini avec les Iteratees. Le suivant est à retenir :

  • Les Iteratees sont immutables.
  • Les Iteratees sont des machines à états (ici StepState).
  • Les Iteratees réagissent aux inputs mais en donnant naissance à un nouveau Iteratee avec un état et probablement une valeur accumulée.
  • Les Iteratees sont autonomes et indépendants : ne sont ni produits ni reliés à un stream particulier (tel est le cas pour les Iterators).
  • Les Iteratees sont non bloquants et le modèle de génération à chaque événement est asynchrone.

Enumerator

C’est le moment de tester nos Iteratees. Pour simplifier notre tâche, on va construire une classe utilitaire qui permet de générer un flux d’input à partir d’un Stream, un fichier ou simplement une Liste. Également, donner à cette classe la possibilité de déléguer à un ensemble d’Iteratees de la parcourir et la consommer. Cette classe sera nommée simplement Enumerator.

Générateur de flux :

Pour satisfaire ce premier rôle, Enumerator est une interface à implémenter pour : les Streams, Fichiers, Sockets, Lists,...

public interface Enumerator<E>

Elle est générique avec E le type de donnée qu’elle manipule.

Consommation des Iteratees :

Pour ce second rôle, cette interface déclare la méthode abstraite apply comme suit :

<A> CompletableFuture<Iteratee<E, A>> apply(Iteratee<E, A> it);

Et pour les impatients, avoir le résultat final du parcours revient à utiliser la méthode run implémentée par défaut au niveau de l’interface Enumerator :

default <B> Input<B> run(Iteratee<E, B> it) {

    switch (it.onState()) {
        case CONT:
          return apply(it).thenApplyAsync(x -> run (x)).join();
        case ERROR:
            Iteratee.Error e = (Iteratee.Error) it;
            throw new RuntimeException(e.getMsg());
        case Done:
            Iteratee.Done<E, B> d = (Iteratee.Done) it;
            return Input.el(d.getA());
        default:
            throw new IllegalStateException();
    }
}

C’est une méthode récursive qui agit selon l'état de l’Iteratee :

  • Si elle est à l’état Done : elle retourne le résultat calculé
  • Si elle est à l’état Error elle génère une exception
  • Si elle est à à l’état Cont : appel à la methode apply (consommation de l’élément suivant) ensuite, appel à run pour la suite des éléments. Il à noter que thenApplyAsync utilise ForkJoinPool.commonPool() par défaut pour les traitements asynchrones.

Pour les tests, on aura recours à une factory qui permet, à partir d’une liste, de construire un enumerator.

static <B> Enumerator<B> enumInput(final Input<B>... input) 

L’implémentation de cette méthode revient à traiter 3 cas :

Cas 1 : une liste vide

On aura besoin de générer un Enumerator Vide

static <E> Enumerator<E> empty() {
        return new Enumerator<E>() {
            @Override
            public <A> CompletableFuture<Iteratee<E, A>> apply(Iteratee<E, A> it) {
                return CompletableFuture.completedFuture(it);
            }
        };
    }
return Enumerator.empty();

Cas 2 : une liste avec un seul élément

On va retourner un Enumerator et on aura besoin d’implementer la méthode apply.

return new Enumerator<B>() {
    @Override
    public <A> CompletableFuture<Iteratee<B, A>> apply(Iteratee<B, A> i) {

        return i.handle((Iteratee<B, A> t) -> {
                switch (t.onState()) {
                    case CONT:
                        return CompletableFuture.supplyAsync(() -> {
                            Iteratee.Cont<B, A> c = (Iteratee.Cont) t;
                            return c.getK().apply(input[0]);
                        });
                    default:
                        return CompletableFuture.completedFuture(t);
                    }
                });
        }
};

Cas 3 : plus qu’un élément

On va déléguer à enumSeq le process du traitement

List<Input<B>> of = Arrays.asList(input);
return new Enumerator<B>() {
    @Override
    public <A> CompletableFuture<Iteratee<B, A>> apply(Iteratee<B, A> it) {
         return enumSeq(of, it);
    }
};

avec enumSeq

static <E, A> CompletableFuture<Iteratee<E, A>> enumSeq(List<Input<E>> l, Iteratee<E, A> i) {
BiFunction<Iteratee<E, A>,Input<E> , CompletableFuture<Iteratee<E, A>>> f = ( Iteratee<E, A> it,Input<E> a) -> {

    switch (it.onState()) {
        case CONT:
            return CompletableFuture.supplyAsync(() -> it.handler().apply(a));

        default:
            return CompletableFuture.completedFuture(it);
        }
};
    return CollectionUtils.leftFoldM( i, l,f);
}

BiFunction est une interface qui accepte deux types d’inputs et génère un résultat. Elle ressemble à :

public interface BiFunction<T, U, R> {
    R apply(T t, U u);
}

Tests

L’exercice est de mettre à jour nos Iteratees avant de lancer les tests.

SumIteratee

Notre SumIteratee sera légèrement modifiée comme suit :

public class SumIteratee implements Iteratee<Integer, Integer> {

    private final Integer sum;

    private final Function<Input<Integer>, Iteratee<Integer, Integer>> handler;

    public SumIteratee(Integer sum, Function<Input<Integer>, Iteratee<Integer, Integer>> handler) {
        this.sum = sum;
        this.handler = handler;
    }

    public SumIteratee(Integer sum) {
        this(0, handler(sum));
    }

    public SumIteratee() {
        this(0);
    }

    public Integer getSum() {
        return sum;
    }

    @Override
    public <B> CompletableFuture<B> handle(Function<Iteratee<Integer, Integer>, CompletableFuture<B>> step) {
        return step.apply(Iteratee.Cont(handler));
    }

    @Override
    public Function<Input<Integer>, Iteratee<Integer, Integer>> handler() {
        return handler;
    }

    private static Function<Input<Integer>, Iteratee<Integer, Integer>> handler(int initValue) {
        return (Input<Integer> e) -> {
            switch (e.onState()) {
                case EL:
                    Input.El<Integer> el = (Input.El) e;
                    Integer elem = el.getE();
                    return new SumIteratee(initValue + elem);
                case EOF:
                    return Iteratee.Done(initValue);
                case Empty:
                default:
                    return new SumIteratee(initValue);
            }
        };
    }
}

MaxIteratee

De même pour maxIteratee :

public class MaxIteratee implements Iteratee<Integer, Integer> {

    private final Function<Input<Integer>, Iteratee<Integer, Integer>> handler;
    private final Integer max;

    public MaxIteratee(Integer max) {
        this.max = max;
        this.handler = handler(max);
    }

    public MaxIteratee() {
        this(Integer.MIN_VALUE);
    }

    @Override
    public <B> CompletableFuture<B> handle(Function<Iteratee<Integer, Integer>, CompletableFuture<B>> folder) {
        return folder.apply(Iteratee.Cont(handler));
    }

    private static Integer max(Integer a, Integer b) {
        return (a > b) ? a : b;
    }


    @Override
    public Function<Input<Integer>, Iteratee<Integer, Integer>> handler() {
        return handler;
    }

    private Function<Input<Integer>, Iteratee<Integer, Integer>> handler(int max) {
        return (Input<Integer> e) -> {
            switch (e.onState()) {
                case EL:
                    Input.El<Integer> el = (Input.El) e;
                    Integer elem = el.getE();
                    return new MaxIteratee(max(elem, max));
                case EOF:
                    return Iteratee.Done(max);
                case Empty:
                default:
                    return new MaxIteratee(max);

            }
        };
    }
}

@Test

On pourra rejouer nos tests avec :

@Test
public void complexeSumMaxer(){
    Input[] in1 = {Input.el(5),Input.EMPTY ,Input.el(2),Input.EOF };
    Input[] in2 = {Input.el(1),Input.EMPTY ,Input.el(4), Input.EMPTY , Input.el(3) , Input.EOF };
    Enumerator enumerator1 = Enumerator.enumInput(in1);
    Enumerator enumerator2 = Enumerator.enumInput(in2);
    Iteratee<Integer,Integer> maxer = new MaxIteratee();
    Iteratee<Integer,Integer> summer = new SumIteratee();


    Input result1 = enumerator1.run(summer);
    Input result2 = enumerator2.run(maxer);
    Input result3 = enumerator1.run(maxer);
    Input result4 = enumerator2.run(summer);

    org.junit.Assert.assertSame(result1.onState(), Input.InputState.EL);
    org.junit.Assert.assertSame(result2.onState(), Input.InputState.EL);
    org.junit.Assert.assertSame(result3.onState(), Input.InputState.EL);
    org.junit.Assert.assertSame(result4.onState(), Input.InputState.EL);
    org.junit.Assert.assertSame("should be same",((Input.El) result1).getE(), 7);
    org.junit.Assert.assertSame("should be same",((Input.El) result2).getE(), 4);
    org.junit.Assert.assertSame("should be same",((Input.El) result3).getE(), 5);
    org.junit.Assert.assertSame("should be same",((Input.El) result4).getE(), 8);
}

La nature immutable des Iteratees permet de les rejouer plusieurs fois sur différents Streams de données. Il est à noter qu’on a injecté des EOF à la fin des listes pour obliger explicitement l'arrêt des Iteratees.

Conclusion

Les Iteratees constituent un outil super puissant pour le traitement des flux de données d’une manière asynchrone et non bloquante. Tout au long de cet article, on a construit pas à pas des Iteratees et on a manipulé des flux.

Le code source de l’Iteratee est disponible sous Github : https://github.com/ouertani/ouertanitee que je vous invite à l'enrichir avec des Iteratees ainsi que des factory d’Enumerator.

Encore un point qu’on n’a pas encore discuté : ce sont les Enumeratees, bien expliqués sur le web.

Références

-Understanding Play2 Iteratees for Normal Humans

-Iteratees for imperative programmers

A propos de l'auteur

Slim OUERTANI est un architecte logiciel avec une expérience dans le monde télécoms et systèmes d’information. Il a participé à la construction et la mise en place de plusieurs solutions notamment au sein de multinationales. Certifié JAVA, SPRING et MongoDB, Slim est passionné par Scala et JEE.

Vous pouvez en savoir plus sur ses récents travaux sur son blog et le suivre sur Twitter : @ouertani.

Bonjour étranger!

Vous devez créer un compte InfoQ ou cliquez sur pour déposer des commentaires. Mais il y a bien d'autres avantages à s'enregistrer.

Tirez le meilleur d'InfoQ

Donnez-nous votre avis

Html autorisé: a,b,br,blockquote,i,li,pre,u,ul,p

M'envoyer un email pour toute réponse à l'un de mes messages dans ce sujet
Commentaires de la Communauté

Html autorisé: a,b,br,blockquote,i,li,pre,u,ul,p

M'envoyer un email pour toute réponse à l'un de mes messages dans ce sujet

Html autorisé: a,b,br,blockquote,i,li,pre,u,ul,p

M'envoyer un email pour toute réponse à l'un de mes messages dans ce sujet

Discuter

Contenu Éducatif

Rien ne serait possible sans le soutien et la confiance de nos Sponsors Fondateurs:

AppDynamics   CloudBees   Microsoft   Zenika
Feedback Général
Bugs
Publicité
Éditorial
InfoQ.com et tous les contenus sont copyright © 2006-2014 C4Media Inc. InfoQ.com est hébergé chez Contegix, le meilleur ISP avec lequel nous ayons travaillé.
Politique de confidentialité
BT