1 - Le paradigme fonctionnel et le paradigme impératif sont deux approches différentes de la programmation. Le paradigme fonctionnel se concentre sur les fonctions et leur composition pour résoudre des problèmes, tandis que le paradigme impératif se concentre sur les instructions et la modification de l'état d'un programme pour atteindre un résultat.
2 - L'évaluation paresseuse est une technique utilisée dans le paradigme fonctionnel pour ne pas évaluer une expression tant que cela n'est pas nécessaire. Cela permet d'économiser des ressources et de travailler avec des structures de données potentiellement infinies. Par exemple, en Haskell, l'expression take 5 [1..] ne va évaluer que les 5 premiers éléments de la liste infinie [1..].
L'instanciation partielle des fonctions est une technique qui permet de créer une nouvelle fonction à partir d'une fonction existante en lui appliquant certains de ses arguments. Par exemple, si f x y = x + y, alors g = f 5 est une nouvelle fonction qui prend un argument y et retourne 5 + y.
3 - Les signatures des fonctions demandées sont :
- map :: (a -> b) -> [a] -> [b]
- unZip :: [(a, b)] -> ([a], [b])
- zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
- foldr :: (a -> b -> b) -> b -> [a] -> b
- foldl :: (b -> a -> b) -> b -> [a] -> b
4 - Les résultats des expressions sont :
- (a) map (\x -> x * x) [3, 2, 1] va appliquer la fonction lambda à chaque élément de la liste [3, 2, 1] et retourner [9, 4, 1].
- (b) foldl (++) [] [[900, 900], [800, 800]] va concaténer les listes [900, 900] et [800, 800] avec ++, puis concaténer le résultat avec la liste vide []. Le résultat final est [900, 900, 800, 800].
- (c) foldl (+) 0 [50, 40, 30] va ajouter chaque élément de la liste [50, 40, 30] à l'accumulateur initial 0, pour obtenir 120.
- (d) foldr (:) [100, 110] [120, 130] va ajouter l'élément 130 à la liste [100, 110] en position de tête, puis ajouter l'élément 120 à la position de tête du résultat précédent. Le résultat final est [120, 130, 100, 110].
- (e) foldr (++) [] [[60, 60], [70, 70]] va concaténer les listes [60, 60] et [70, 70] avec ++, puis concaténer le résultat avec la liste vide []. Le résultat final est [60, 60, 70, 70].
-
foldr (*) 0 [10, 2, 3] va multiplier chaque élément de la liste `[10, 2, 3]` en partant de la droite (d'où le "r" dans "foldr") en utilisant la valeur initiale 0. 1ère étape : 3 * 0 = 0 2ème étape : 2 * 0 = 0 3ème étape : 10 * 0 = 0, Le résultat final est donc 0.
Solution plus detaillee :
(a) map (\x → xx) [3, 2, 1]
map (\x → xx) [3, 2, 1]
= (\x → xx) 3 : map (\x → xx) [2, 1]
= 33 : (\x → xx) 2 : map (\x → xx) [1]
= 9 : 22 : (\x → xx) 1 : map (\x → xx) []
= 9 : 4 : 11 : map (\x → xx) []
= 9 : 4 : 1 : []
= [9, 4, 1]
(b) foldl (++) [] [[900, 900], [800, 800]]
foldl (++) [] [[900, 900], [800, 800]]
= foldl (++) ([900, 900]) [[800, 800]]
= foldl (++) ([900, 900]) ([800, 800] ++ [])
= foldl (++) ([900, 900, 800, 800]) []
= [900, 900, 800, 800]
(c) foldl (+) 0 [50, 40, 30]
foldl (+) 0 [50, 40, 30]
= foldl (+) 50 [40, 30]
= foldl (+) 90 [30]
= foldl (+) 120 []
= 120
(d) foldr (:) [100, 110] [120, 130]
foldr (:) [100, 110] [120, 130]
= 120 : foldr (:) [100, 110] [130]
= 120 : 130 : foldr (:) [100, 110] []
= 120 : 130 : [100, 110]
= [120, 130, 100, 110]
(e) foldr (++) [] [[60, 60], [70, 70]]
foldr (++) [] [[60, 60], [70, 70]]
= [60, 60] ++ foldr (++) [] [[70, 70]]
= [60, 60] ++ ([70, 70] ++ foldr (++) [] [])
= [60, 60] ++ [70, 70]
= [60, 60, 70, 70]
(f) foldr () 0 [10, 2, 3]
foldr () 0 [10, 2, 3]
= 10 * foldr () 0 [2, 3]
= 10 * (2 * foldr () 0 [3])
= 10 * (2 * (3 * foldr (*) 0 []))
= 10 * (2 * (3 * 0))
= 0
5) maxList :: Ord a => [a] -> Maybe a maxList [] = Nothing maxList xs = Just $ maximum xs
6) retElem :: [a] -> [a] retElem [] = [] retElem [x] = [] retElem (x:y:[]) = [x] retElem (x:xs) = x : retElem xs
7) data Etudiant a b c = Etudiant {nom::a, matricule::b, ville::c}
getNom :: Etudiant a b c -> a getNom (Etudiant nom _ _) = nom
getMatricule :: Etudiant a b c -> b getMatricule (Etudiant _ matricule _) = matricule
getVille :: Etudiant a b c -> c getVille (Etudiant _ _ ville) = ville
8)
a) List p représente une liste dont les éléments sont de type p. Empty représente une liste vide et Suite représente un élément de la liste composé d'une tête (deb) de type p et d'une queue (resiList) de type List p.
(b) Empty est de type List p, Suite est de type p -> List p -> List p, resiList est de type List p et deb est de type p.
(c) Non, il n'est pas possible de donner le type de List p car p est un type polymorphe et n'a pas de contraintes spécifiques.
(d) Suite {deb=100, resiList=Suite {deb=200, resiList=Suite {deb=300, resiList=Suite {deb=400, resiList=Empty}}}} représente [100, 200, 300, 400] dans le type List p.
(e) Voici une implémentation de sommeList pour la liste définie ci-dessus:
sommeList :: Num a => List a -> a
sommeList Empty = 0
sommeList (Suite x xs) = x + sommeList xs
(f) Voici les instances de Eq, Ord, Show et Enum pour le type List p:
instance Eq p => Eq (List p) where
Empty == Empty = True
(Suite x xs) == (Suite y ys) = x == y && xs == ys
_ == _ = False
instance Ord p => Ord (List p) where
compare Empty Empty = EQ
compare Empty _ = LT
compare _ Empty = GT
compare (Suite x xs) (Suite y ys) =
case compare x y of
EQ -> compare xs ys
LT -> LT
GT -> GT
instance Show p => Show (List p) where
show Empty = "[]"
show (Suite x Empty) = "[" ++ show x ++ "]"
show (Suite x xs) = "[" ++ show x ++ ", " ++ show xs ++ "]"
instance Enum p => Enum (List p) where
toEnum n = Suite (toEnum n) Empty
fromEnum Empty = error "Cannot convert empty list to Enum"
fromEnum (Suite x xs) = fromEnum x
succ Empty = error "Cannot get successor of empty list"
succ (Suite x xs) = Suite (succ x) Empty
pred Empty = error "Cannot get predecessor of empty list"
pred (Suite x xs) = Suite (pred x) Empty
Notes Question 6, 7, 8 : Les types polymorphes sont des types qui peuvent prendre n'importe quelle valeur d'un type donné. En Haskell, on utilise généralement des lettres minuscules pour représenter ces types polymorphes. Par exemple, a, b, c, etc. sont des lettres qui peuvent représenter n'importe quel type.
Lorsqu'on donne une signature de type polymorphe, on utilise ces lettres pour représenter les types des arguments et du résultat de la fonction, sans spécifier un type particulier. Par exemple, la signature de la fonction maxList peut être écrite comme maxList :: Ord a => [a] -> Maybe a. Ici, a représente n'importe quel type qui peut être ordonné, et la fonction renvoie un Maybe a pour gérer le cas où la liste est vide.
De même, la signature des fonctions getNom, getMatricule et getVille peut être écrite comme getNom :: Etudiant a b c -> a, getMatricule :: Etudiant a b c -> b et getVille :: Etudiant a b c -> c, respectivement. Ici, Etudiant a b c représente un type qui contient un nom de type a, un matricule de type b et une ville de type c. Les fonctions renvoient simplement le nom, le matricule ou la ville, respectivement.
Enfin, pour la définition de la liste List p, Empty représente une liste vide, Suite {deb::p, resiList::List p} représente une liste non vide, où deb est le premier élément et resiList est le reste de la liste. resiList est également une liste de type List p.