• Aucun résultat trouvé

map

map prend une fonction et une liste, et applique la fonction à tous les éléments de la liste, résultant en une nouvelle liste. Regardons sa signature et sa définition.

map :: (a -> b) -> [a] -> [b]

map _ [] = []

map f (x:xs) = f x : map f xs

La signature dit qu’elle prend une fonction de a vers ba b , une liste de aa , et retourne une liste de bb . Il est intéressant de voir que rien qu’en regardant la signature d’une fonction, vous pouvez parfois dire exactement ce qu’elle fait. mapmap est une de ces fonctions d’ordre supérieur extrèmement utile qui peut être utilisée de milliers de façons différentes. La voici en action :

ghci> map (+3) [1,5,3,1,6] [4,8,6,4,9]

ghci> map (++ "!") ["BIFF", "BANG", "POW"] ["BIFF!","BANG!","POW!"]

ghci> map (replicate 3) [3..6] [[3,3,3],[4,4,4],[5,5,5],[6,6,6]]

ghci> map (map (^2)) [[1,2],[3,4,5,6],[7,8]] [[1,4],[9,16,25,36],[49,64]]

ghci> map fst [(1,2),(3,5),(6,3),(2,6),(2,5)] [1,3,6,2,2]

Vous avez probablement remarqué que chacun de ces exemples aurait pu être réalisé à l’aide de listes en compréhension. map (+3) [1, 5, 3, 1, 6]

map (+3) [1, 5, 3, 1, 6] est équivalent à [x+3 | x <- [1, 5, 3, 1, 6]][x+3 | x <- [1, 5, 3, 1, 6]] . Cependant, utiliser mapmap est bien plus lisible dans certains cas où vous ne faites qu’appliquer une fonction à chaque élément de la liste, surtout quand vous mappez un map auquel cas les crochets s’entassent de manière déplaisante.

filter

filter est une fonction qui prend un prédicat (un prédicat est une fonction qui dit si quelque chose est vrai ou faux, une fonction qui retourne une valeur booléenne) et une liste, et retourne la liste des éléments qui satisfont le prédicat. La signature et l’implémentation :

filter :: (a -> Bool) -> [a] -> [a]

filter _ [] = []

filter p (x:xs)

| p x = x : filter p xs | otherwise = filter p xs

Plutôt simple. Si p xp x vaut TrueTrue , l’élément est inclus dans la nouvelle liste. Sinon, il reste dehors. Quelques exemples d’utilisation :

ghci> filter (>3) [1,5,3,2,1,6,4,3,2,1] [5,6,4]

ghci> filter (==3) [1,2,3,4,5] [3]

ghci> filter even [1..10] [2,4,6,8,10]

ghci> let notNull x = not (null x) in filter notNull [[1,2,3],[],[3,4,5],[2,2],[],[],[]] [[1,2,3],[3,4,5],[2,2]]

ghci> filter (`elem` ['a'..'z']) "u LaUgH aT mE BeCaUsE I aM diFfeRent" "uagameasadifeent"

ghci> filter (`elem` ['A'..'Z']) "i lauGh At You BecAuse u r aLL the Same" "GAYBALLS"

Tout ceci pourrait aussi être fait à base de listes en compréhension avec des prédicats. Il n’y a pas de règle privilégiant l’utilisation de mapmap et filter

filter à celle des listes en compréhension, vous devez juste décider de ce qui est plus lisible en fonction du code et du contexte. L’équivalent pour filter de l’application de plusieurs prédicats dans une liste en compréhension est soit en filtrant plusieurs fois d’affilée, soit en fusionnantfilter des prédicats à l’aide de && .&&

Vous souvenez-vous de la fonction quicksort du chapitre précédent ? Nous avions utilisé des listes en compréhension pour filtrer les éléments plus petits que le pivot. On peut faire de même avec filterfilter :

quicksort :: (Ord a) => [a] -> [a]

quicksort [] = []

quicksort (x:xs) =

let smallerSorted = quicksort (filter (<=x) xs) biggerSorted = quicksort (filter (>x) xs) in smallerSorted ++ [x] ++ biggerSorted

Mapper et filtrer sont le marteau et le clou de la boîte à outils de tout programmeur fonctionnel. Bon, il importe peut que vous utilisiez plutôt mapmap et filterfilter que des listes en compréhension. Souvenez-vous comment nous avions résolu le problème de trouver les triangles rectangles avec un certain périmètre. En programmation impérative, nous aurions imbriqué trois boucles, dans lequelles nous aurions testé si la combinaison courante correspondait à un triangle rectangle et avait le bon périmètre, auquel cas nous en aurions fait quelque chose comme l’afficher à l’écran. En programmation fonctionnelle, ceci est effectué par mappage et filtrage. Vous créez une fonction qui prend une valeur et produit un résultat. Vous mappez cette fonction sur une liste de valeurs, puis vous filtrez la liste résultante pour obtenir les résultats qui satisfont votre recherche. Et grâce à la paresse d’Haskell, même si vous mappez et filtrez sur une liste plusieurs fois, la liste ne sera traversée qu’une seule fois.

Trouvons le plus grand entier inférieur à 100 000 qui soit divisible par 3 829. Pour cela, filtrons un ensemble de solutions pour lesquelles on sait que le résultat s’y trouve.

largestDivisible :: (Integral a) => a

largestDivisible = head (filter p [100000,99999..]) where p x = x `mod` 3829 == 0

On crée d’abord une liste de tous les entiers inférieurs à 100 000 en décroissant. Puis, on la filtre par un prédicat, et puisque les nombres sont en ordre décroissant, le plus grand d’entre eux sera simplement le premier élément de la liste filtrée. On n’a même pas eu besoin d’une liste finie pour démarrer. Encore un signe de la paresse en action. Puisque l’on n’utilise que la tête de la liste filtrée, peu importe qu’elle soit finie ou infinie. L’évaluation s’arrête dès que la première solution est trouvée.

Maintenant, trouvons la somme de tous les carrés impairs inférieurs à 10 000. Mais d’abord, puisqu’on va l’utiliser dans la solution,

introduisons la fonction takeWhile . Elle prend un prédicat et une liste, et parcourt cette liste depuis le début en retournant tous les éléments tanttakeWhile que le prédicat reste vrai. Dès qu’un élément ne satisfait plus le prédicat, elle s’arrête. Si l’on voulait le premier mot d’une chaîne comme

"elephants know how to party"

"elephants know how to party" , on pourrait faire takeWhile (/=' ') "elephants know how to party"takeWhile (/=' ') "elephants know how to party" et cela retournerait "elephants"

"elephants" . Ok. La somme des carrés impairs inférieurs à dix mille. D’abord, on va commencer par mapper (^2)(^2) sur la liste infinie [1..][1..] . Ensuite, on va filtrer pour garder les nombres impairs. Puis, on prendra des éléments de cette liste tant qu’ils restent inférieurs à 10 000. Enfin, on va sommer cette liste. On n’a même pas besoin d’une fonction pour ça, une ligne dans GHCi suffit :

ghci> sum (takeWhile (<10000) (filter odd (map (^2) [1..])))

166650

Génial ! On commence avec une donnée initiale (la liste infinie de tous les entiers naturels) et on mappe par dessus, puis on filtre et on coupe jusqu’à avoir ce que l’on désire, et on somme le tout. On aurait pu écrire ceci à l’aide de listes en compréhension :

ghci> sum (takeWhile (<10000) [n^2 | n <- [1..], odd (n^2)])

166650

C’est une question de goût, à vous de choisir la forme que vous préférez. Encore une fois, la paresse d’Haskell rend ceci possible. On peut mapper et filtrer une liste infinie, puisqu’il ne va pas mapper et filtrer directement, il retardera ces actions. C’est seulement quand on demande à Haskell de nous montrer la somme que la fonction sumsum dit à la fonction takeWhiletakeWhile qu’elle a besoin de cette liste de nombre. takeWhiletakeWhile force le filtrage et le mappage, en demandant des nombres un par un tant qu’il n’en croise pas un plus grand que 10000.

Pour notre prochain problème, on va se confronter aux suites de Collatz. On prend un entier naturel. Si cet entier est pair, on le divise par deux. S’il est impair, on le multiplie par 3 puis on ajoute 1. On prend ce nombre, et on recommence, ce qui produit un nouveau nombre, et ainsi de suite. On obtient donc une chaîne de nombres. On pense que, quel que soit le nombre de départ, la chaîne termine, avec pour valeur 1. Si on prend 13, on obtient la suite : 13, 40, 20, 10, 5, 16, 8, 4, 2, 1. 13 fois 3 plus 1 vaut 40. 40 divisé par 2 vaut 20, etc. On voit que la chaîne contient 10 termes. Maintenant on cherche à savoir : pour tout nombre de départ compris entre 1 et 100, combien de chaînes ont une longueur supérieure

à 15 ? D’abord, on va écrire une fonction qui produit ces chaînes :

chain :: (Integral a) => a -> [a]

chain 1 = [1]

chain n

| even n = n:chain (n `div` 2) | odd n = n:chain (n*3 + 1)

Puisque les chaînes terminent à 1, c’est le cas de base. C’est une fonction récursive assez simple.

ghci> chain 10 [10,5,16,8,4,2,1] ghci> chain 1 [1] ghci> chain 30 [30,15,46,23,70,35,106,53,160,80,40,20,10,5,16,8,4,2,1]

Yay ! Ça a l’air de marcher correctement. À présent, la fonction qui nous donne notre réponse :

numLongChains :: Int

numLongChains = length (filter isLong (map chain [1..100])) where isLong xs = length xs > 15

On mappe la fonction chainchain sur la liste [1..100][1..100] pour obtenir une liste de toutes les chaînes, qui sont elles-même représentées sous forme de listes. Puis, on les filtre avec un prédicat qui vérifie simplement si la longueur est plus grande que 15. Une fois ceci fait, il ne reste plus qu’à compter le nombre de chaînes dans la liste finale.

Note : Cette fonction a pour type numLongChains :: Int parce que lengthnumLongChains :: Int length a pour type IntInt au lieu de Num aNum a , pour des raisons historiques. Si nous voulions retourner Num aNum a , on aurait pu utiliser fromIntegralfromIntegral sur le nombre retourné.

En utilisant mapmap , on peut faire des trucs comme map (*) [0..]map (*) [0..] , par exemple pour illustrer la curryfication et le fait que les fonctions appliquées partiellement sont de vraies valeurs que l’on peut manipuler et placer dans des listes (par contre, on ne peut pas les transformer en chaînes de caractères). Pour l’instant, on a seulement mappé des fonctions qui attendent un paramètre sur des listes, comme map (*2) [0..] pour obtenirmap (*2) [0..] des listes de type (Num a) => [a] , mais on peut aussi faire map (*) [0..](Num a) => [a] map (*) [0..] sans problème. Ce qui se passe ici, c’est que le nombre dans la liste s’applique à la fonction * , qui a pour type (Num a) => a -> a -> a* (Num a) => a -> a -> a . Appliquer une fonction sur un paramètre, alors qu’elle en attend deux, retourne une fonction qui attend encore un paramètre. Si l’on mappe ** sur [0..][0..] , on obtient en retour une liste de fonctions qui attendent chacune un paramètre, donc (Num a) [a -> a] . map (*) [0..](Num a) [a -> a] map (*) [0..] produit une liste comme celle qu’on obtiendrait en écrivant

[(0*), (1*), (2*), (3*), (4*), (5*), …] [(0*), (1*), (2*), (3*), (4*), (5*), …] .

ghci> let listOfFuns = map (*) [0..]

ghci> (listOfFuns !! 4) 5 20

Demander l’élément d’index 4 de notre liste retourne une fonction équivalente à (4*)4 (4*) . Puis on applique cette fonction à 55 . Donc c’est comme écrire (4*) 5 ou juste 4 * 5(4*) 5 4 * 5 .

Lambdas

Les lambda expressions sont simplement des fonctions anonymes utilisées lorsqu’on a besoin d’une fonction à usage unique. Normalement, on crée une lambda uniquement pour la passer à une fonction d’ordre supérieur. Pour créer une lambda, on écrit un \ (parce que ça ressemble à la lettre grecque lambda si vous le fixez assez\ longtemps des yeux) puis les paramètres, séparés par des espaces. Après cela vient -> puis le corps de la-> fonction. On l’entoure généralement de parenthèses, autrement elle s’étend autant que possible vers la droite. Si vous regardez 10cm plus haut, vous verrez qu’on a utilisé une liaison where dans numLongChains pournumLongChains créer la fonction isLong , avec pour seul but de passer cette fonction à filterisLong filter . Au lieu de faire ça, on pourrait utiliser une lambda :

numLongChains :: Int

numLongChains = length (filter (\xs -> length xs > 15) (map chain [1..100]))

Les lambdas sont des expressions, c’est pourquoi on peut les manipuler comme ça. L’expression (\xs -> length xs > 15) retourne une(\xs -> length xs > 15) fonction qui nous dit si la longueur de la liste passée en paramètre est plus grande que 15.

Les personnes n’étant pas habituées à la curryfication et à l’application partielle de fonctions utilisent souvent des lambdas là où ils n’en ont en fait pas besoin. Par exemple, les expressions map (+3) [1, 6, 3, 2]map (+3) [1, 6, 3, 2] et map (\x -> x + 3) [1, 6, 3, 2]

map (\x -> x + 3) [1, 6, 3, 2] sont équivalentes puisque (+3)(+3) tout comme (\x -> x + 3)(\x -> x + 3) renvoient toutes les deux le nombre passé en paramètre plus 3. Il est inutile de vous préciser que créer une lambda dans ce cas est stupide puisque l’application partielle est bien plus lisible.

Comme les autres fonctions, les lambdas peuvent prendre plusieurs paramètres.

ghci> zipWith (\a b -> (a * 30 + 3) / b) [5,4,3,2,1] [1,2,3,4,5] [153.0,61.5,31.0,15.75,6.6]

Et comme les fonctions, vous pouvez filtrer par motif dans les lambdas. La seule différence, c’est que vous ne pouvez pas définir plusieurs motifs pour un paramètre, comme par exemple créer un motif [] et un (x:xs)[] (x:xs) pour le même paramètre, et avoir les valeurs parcourir les différents motifs. Si un filtrage par motif échoue dans une lambda, une erreur d’exécution a lieu, donc faites attention en filtrant dans les lambdas.

ghci> map (\(a,b) -> a + b) [(1,2),(3,5),(6,3),(2,6),(2,5)] [3,8,9,8,7]

Les lambdas sont habituellement entourées de parenthèses à moins qu’on veuille qu’elles s’étendent le plus possible à droite. Voilà quelque chose d’intéressant : vu la façon dont sont curryfiées les fonctions par défaut, ces deux codes sont équivalents :

addThree :: (Num a) => a -> a -> a -> a

addThree x y z = x + y + z

addThree :: (Num a) => a -> a -> a -> a

addThree = \x -> \y -> \z -> x + y + z

Si on définit une fonction ainsi, il devient évident que la signature doit avoir cette forme. Il y a trois ->-> dans le type et dans l’équation. Mais bien sûr, la première façon d’écrire est bien plus lisible, la seconde ne sert qu’à illustrer la curryfication.

Cependant, cette notation s’avère parfois pratique. Je trouve la fonction flipflip plus lisible définie ainsi :

flip' :: (a -> b -> c) -> b -> a -> c

flip' f = \x y -> f y x

Bien que ce soit comme écrire flip' f x y = f y x , on fait apparaître clairement que ce sera utilisé pour créer une nouvelle fonction laflip' f x y = f y x plupart du temps. L’utilisation la plus classique de flipflip consiste à appeler la fonction avec juste une autre fonction, et de passer le résultat à un

map

map ou un filterfilter . Utilisez donc les lambdas lorsque vous voulez rendre explicite le fait que cette fonction est généralement appliquée partiellement avant d’être passée à une autre comme paramètre.