• Aucun résultat trouvé

Maintenant que l’on sait penser en termes de récursivité, essayons d’implémenter quelques fonctions récursives. D’abord, nous allons implémenter replicate

replicate . replicatereplicate prend un IntInt et un élément, et retourne une liste qui a plusieurs fois cet élément. Par exemple, replicate 3 5replicate 3 5 retourne [5, 5, 5][5, 5, 5] . Réfléchissons au cas de base. À mon avis, le cas de base correspond à des valeurs inférieures ou égales à 0. Si l’on essaie de répliquer quelque chose zéro fois, on devrai renvoyer une liste vide. Et pareil pour des nombres négatifs, sinon ça ne veut pas dire grand chose.

replicate' :: (Num i, Ord i) => i -> a -> [a]

replicate' n x | n <= 0 = []

| otherwise = x:replicate' (n-1) x

Nous avons utilisé des gardes ici plutôt que des motifs car on teste une condition booléenne. Si n est plus petit que 0, retourner la liste vide. Sinon,n retourner une liste qui a xx comme premier élément, et ensuite xx répliqué n-1 fois pour la queue. À force, la partie en (n-1)(n-1) va amener notre fonction à atteindre son cas de base.

Note : Num n’est pas une sous-classe d’ OrdNum Ord . Cela signifie qu’un nombre n’a pas forcément besoin d’être ordonnable. C’est pourquoi on doit spécifier à la fois Num et OrdNum Ord en contraintes de classe pour pouvoir faire à la fois des additions, des soustractions et des comparaisons.

Maintenant, implémentons taketake . Elle prend un certain nombre d’éléments d’une liste. Par exemple, take 3 [5, 4, 3, 2, 1]take 3 [5, 4, 3, 2, 1] retourne [5, 4, 3]

[5, 4, 3] . Si on essaie de prendre 0 ou moins éléments d’une liste, on récupère une liste vide. Également, si l’ont essaie de prendre quoi que ce soit d’une liste vide, on récupère une liste vide. Remarquez que ce sont nos deux cas de base. Écrivons tout cela :

take' :: (Num i, Ord i) => i -> [a] -> [a]

take' n _

| n <= 0 = []

take' _ [] = []

Le premier motif spécifie qu’essayer de prendre 0 ou moins éléments retourne une liste vide. Remarquez qu’on utilise __ pour filtrer la liste parce que nous ne nous intéressons pas vraiment à sa valeur dans ce cas. Remarquez aussi qu’on utilise des gardes, mais pas otherwise . Cela veut dire que si notherwise n s’avère être supérieur à 0, le filtrage passe au motif suivant. Le deuxième motif indique que si l’on essaie de prendre quoi que ce soit dans une liste vide, on récupère une liste vide. Le troisième motif coupe la liste en tête et queue. Puis, on dit que prendre nn éléments d’une liste équivaut à une liste avec xx en tête et une liste qui prend n-1 éléments dans le reste pour queue. Essayez de prendren-1 une feuille de papier pour écrire les étapes de l’évaluation de take 3 [4, 3, 2, 1]take 3 [4, 3, 2, 1] .

reverse

reverse renverse une liste. Pensez au cas de base. Quel est-il ? Allons… c’est la liste vide ! Une liste vide renversée est la liste vide elle-même. Ok. Et le reste ? Eh bien, on peut dire que si l’on coupe une liste en tête et queue, la liste renversée est égale à la queue renversée, avec la tête à la fin.

reverse' :: [a] -> [a]

reverse' [] = []

reverse' (x:xs) = reverse' xs ++ [x] Et voilà !

Puisqu’Haskell supporte des listes infinies, notre récursivité n’a pas vraiment besoin de cas de base. Si elle n’en a pas, elle va soit continuer à s’agiter sur un calcul à l’infini, ou produire une structure de donnée infinie, comme une liste infinie. La chose bien à propos des listes infinies, c’est qu’on peut les couper où on veut. repeat prend un élément et retourne une liste infinie qui a cet élément uniquement. Une implémentationrepeat récursive est très simple, regardez.

repeat' :: a -> [a]

repeat' x = x:repeat' x

Appeler repeat 3repeat 3 nous donnera une liste qui commence avec un 33 , et a une infinité de 33 en queue. Appeler repeat 3repeat 3 s’évaluera à 3:repeat 3

3:repeat 3 , puis à 3:(3:repeat 3)3:(3:repeat 3) , puis à 3:(3:(3:repeat 3))3:(3:(3:repeat 3)) , etc. repeat 3repeat 3 ne terminera jamais son évaluation, alors que take 5 (repeat 3)

take 5 (repeat 3) nous donnera une liste de cinq 3. C’est comme si on avait fait replicate 5 3replicate 5 3 . zip

zip prend deux listes, et les zippe ensemble. zip [1, 2, 3] [2, 3]zip [1, 2, 3] [2, 3] retourne [(1, 2), (2, 3)][(1, 2), (2, 3)] , parce qu’elle tronque la plus grande liste pour avoir la même taille que l’autre. Et si l’on zippe quelque chose à la liste vide ? Eh bien, on obtient la liste vide. Voilà notre cas de base. Cependant, zip prend deux listes en paramètres, donc il y a en fait deux cas de base.zip

zip' :: [a] -> [b] -> [(a,b)]

zip' _ [] = []

zip' [] _ = []

zip' (x:xs) (y:ys) = (x,y):zip' xs ys

D’abord, les deux premiers motifs disent que si une des deux listes est vide, on obtient une liste vide. Le troisième dit que deux listes zippées sont égales à la paire de leur tête, suivie de leurs queues zippées. Zipper [1, 2, 3] et ['a', 'b'][1, 2, 3] ['a', 'b'] va finalement essayer de zipper [3][3] et [][] . Le cas de base arrive et le résultat est donc (1, 'a'):(2, 'b'):[] , qui est exactement la même chose que [(1, 'a'), (2, 'b')](1, 'a'):(2, 'b'):[] [(1, 'a'), (2, 'b')] . Implémentons encore une fonction de la bibliothèque standard - elem . Elle prend un élément et une liste et vérifie si cet élément appartient à laelem liste. La condition de base, comme souvent, est la liste vide. On sait qu’une liste vide ne contient aucun élément, donc elle ne contient certainement pas les droïdes que vous recherchez.

elem' :: (Eq a) => a -> [a] -> Bool

elem' a [] = False

elem' a (x:xs)

| a == x = True

| otherwise = a `elem'` xs