Erlang – exemple 05

Cet article propose quelques exemples de ce qu'on nomme des List Comprehensions en Erlang. Le mécanisme des List Comprehensions permet des écritures compactes d'expressions appliquées sur des listes (j'ai conservé l'écriture anglaise du fait que « Compréhensions de listes » ne me semblait pas vraiment plus clair).

Remarques Code

Par exemple, en reprenant le funs:for_each/2 de l'exemple 04, on pourrait produire une liste qui contiendrait, élément par élément, le double du contenu d'une autre liste. Nous utilisons ici le fun d'arité 1 associé à Carre et appliquée à travers for_each/2 à l'instruction 2>.

Pour obtenir le même résultat à l'aide de List Comprehensions, on peut utiliser la notation à la ligne 4> qui utilise la liste Liste définie à la ligne 3>. Ce qu'exprime la ligne 4> est une liste telle que ce qu'on obtiendrait en appliquant Carre à chaque élément X de la liste Liste.

Erlang R14B (erts-5.8.1.1) [smp:2:2] [rq:2] [async-threads:0]
Eshell V5.8.1.1  (abort with ^G)
1> Carre = fun(X) -> X * X end.
#Fun<erl_eval.6.13229925>
2> funs:for_each([0,1,2,3,4], Carre).
[0,1,4,9,16]
3> Liste = [0,1,2,3,4].
[0,1,2,3,4]
4> [Carre(X) || X <- Liste].
[0,1,4,9,16]
5>

Le résultat est le même dans les deux cas, mais la notation des List Comprehensions est plus compacte.

À titre d'exemple, imaginons un petit programme qui évaluera la distance entre divers points sur une surface euclidienne et un point choisi (ici, l'origine du référentiel choisi).

Remarques Code

L'instruction 1> associe un fun d'arité 2 servant à la fabrication de l'uplet {point,X,Y} à la variable CreerPoint.

Erlang R14B (erts-5.8.1.1) [smp:2:2] [rq:2] [async-threads:0]
Eshell V5.8.1.1  (abort with ^G)
1> CreerPoint = fun(X,Y) -> { point, X, Y } end.
#Fun<erl_eval.12.113037538>

Les fonctions de fabrication constituent un schéma de conception (un Design Pattern) bien connu et très répandu. L'idiome Erlang appliqué ici consiste quant à lui à identifier la nature de l'uplet par un atome (ici, point) lui servant de premier élément.

L'instruction 2> invoque CreerPoint à quelques reprises pour populer une liste associée à la variable Points.

2> Points = [CreerPoint(2,3), CreerPoint(0,0), CreerPoint(-1.5,2.3)].
[{point,2,3},{point,0,0},{point,-1.5,2.3}]

L'instruction 3> associe un fun d'arité 2 servant à évaluer la distance entre deux points à la variable Distance, à l'aide du bon vieux théorème de Pythagore.

3> Distance = fun({point, X0, Y0}, {point, X1, Y1}) ->
3>    math:sqrt(math:pow(X0-X1,2) + math:pow(Y0-Y1,2)) end.
#Fun<erl_eval.12.113037538>

Remarquez l'écriture de la signature de ce fun : elle n'est valable que pour des points correctement formés, au sens de ceux générés par CreerPoint.

Remarquez aussi l'utilisation de quelques fonctions de bibliothèques prédéfinis (ici, les classiques fonctions mathématiques sqrt et pow, toutes deux logées dans le module math).

Les instructions 4> et 5> vérifient le bon fonctionnement de Distance. Tout semble Ok.

4> Distance(CreerPoint(1,1), CreerPoint(1,2)).
1.0
5> Distance(CreerPoint(1,1), CreerPoint(2,2)).
1.4142135623730951

L'instruction 6> définit un fun DistancePoint générant un autre fun, interne et anonyme.

6> DistancePoint = fun(P0) -> (fun(P) -> Distance(P0,P) end) end. 
#Fun<erl_eval.6.13229925>

Nous utiliserons DistancePoint pour transformer une fonction d'arité 2 en une fonction d'arité 1 pour un paramètre fixe (ici : P0) donné. Ceci rappelle certaines pratiques reposant sur des foncteurs en C++.

L'instruction 7> associe à DistanceOrigine un cas particulier de DistancePoint capable de mesurer la distance entre un point P et l'origine du référentiel (le point aux coordonnées 0,0 dans ce cas-ci).

7> DistanceOrigine = DistancePoint(CreerPoint(0,0)).             
#Fun<erl_eval.6.13229925>

Enfin, l'instruction 8> évalue par List Comprehension la distance entre chaque point de Points à l'origine.

8> DistancesOrigine = [DistanceOrigine(P) || P <- Points].               
[3.605551275463989,0.0,2.745906043549196]

La notation est compacte, et indique pour chaque élément P de la liste Points, insère dans la liste générée ici le résultat de DistanceOrigine(P).

Terminons en indiquant qu'il est possible d'utiliser les List Comprehensions à titre de filtre lorsque les listes contiennent des éléments susceptibles de ne pas être conformes aux attentes.

Remarques Code

Prenons à titre d'exemple la liste associée à la variable Pts (à droite). Cette list comprend quelques points correctement formés (selon nos standards), de même qu'une séquence de bytes, "coucou", et qu'un autre uplet, {carre,3}.

L'instruction 10> échoue parce que DistanceOrigine reçoit chaque élément P de la liste Pts, ce qui la nourrira entre autres de points incorrectement formés.

Par contre, l'instruction 11> précise la nature des éléments tirés de Pts en décrivant le Pattern attendu. Ceci filtre la liste à la source et n'applique DistanceOrigine qu'aux éléments conformes.

9> Pts = [CreerPoint(2,3), CreerPoint(1,1), "coucou",
9>        { carre, 3 }, CreerPoint(-1,-2) ].
[{point,2,3},{point,1,1},"coucou",{carre,3},{point,-1,-2}]
10> DistOrig = [DistanceOrigine(P) || P <- Pts].         
** exception error: no function clause matching 
                    erl_eval:'-inside-an-interpreted-fun-'({point,0,0},
                                                           "coucou")
11> DistOrig = [DistanceOrigine({point,X,Y}) || {point,X,Y} <- Pts].
[3.605551275463989,1.4142135623730951,2.23606797749979]

Valid XHTML 1.0 Transitional

CSS Valide !