Improving-and-combining-functions
Amélioration et combinaison de fonctions
Plan
- Fonctions d'ordre supérieur
filterany- Fonctions lambda
- Priorité et associativité
- Fonctions curryfiées
- Application partielle
- Application et composition de fonctions
- L'opérateur
$ - L'opérateur
. - Style sans point (point-free)
Une fonction d'ordre supérieur est une fonction qui prend d'autres fonctions en paramètre ou qui retourne une fonction en résultat.
Puisque nous pouvons passer des fonctions en entrée, les retourner en sortie et les assigner à des variables, elles sont considérées comme des valeurs à part entière. Nous disons donc que les fonctions sont des citoyens de première classe.
Prenons un exemple classique. Imaginons que vous ayez une fonction que vous appliquez généralement deux fois :
complexFunc1 :: Int -> Int
complexFunc1 x = x + 1
func1 :: Int -> Int
func1 x = complexFunc1 (complexFunc1 x)
complexFunc2 :: Int -> Int
complexFunc2 x = x + 2
func2 :: Int -> Int
func2 x = (complexFunc2 (complexFunc2 x)) + (complexFunc2 (complexFunc2 x))C'est un exemple exagéré, mais on peut voir émerger un schéma : vous utilisez toujours complexFunc1 et complexFunc2 deux fois !
Dès que nous identifions ce motif, nous réalisons que nous pouvons améliorer cela. Et si nous créions une fonction qui prend en paramètre une fonction et une valeur, puis applique la fonction à cette valeur deux fois ?
Nous pouvons le faire avec :
applyTwice :: (a -> a) -> a -> a
applyTwice f x = f (f x)Ici, la signature de type est différente des précédentes. La partie (a -> a) indique que le premier paramètre est une fonction prenant une valeur de type a et retournant une valeur du même type. Le second paramètre est une valeur de type a, et la fonction applyTwice retourne aussi une valeur de type a.
Dans le corps de la fonction, nous appliquons f à x, puis nous appliquons encore f au résultat. Nous appliquons donc f deux fois.
Et voilà ! Nous avons créé une fonction d'ordre supérieur !
Nous pouvons maintenant simplifier notre code précédent :
func1' :: Int -> Int
func1' x = applyTwice complexFunc1 x
func2' :: Int -> Int
func2' x = (applyTwice complexFunc2 x) + (applyTwice complexFunc2 x)Ceci est un exemple simple, mais les fonctions d'ordre supérieur sont une fonctionnalité extrêmement puissante. Elles sont omniprésentes en Haskell !
Commençons avec la fonction filter :
:t filter
filter :: forall a. (a -> Bool) -> [a] -> [a]Cette fonction prend un prédicat (a -> Bool) et une liste [a] et filtre les éléments de la liste selon le prédicat.
Exemple : filtrer les nombres pairs d'une liste de 1 à 20 :
filter even [1..20]
[2,4,6,8,10,12,14,16,18,20]Un exemple plus complexe : filtrer les fruits contenant la lettre 'a' :
fruitWithA = filter tempFunct ["Apple", "Banana", "Pear", "Grape", "Wood"]
where tempFunct x = 'a' `elem` x
fruitWithA
["Banana","Pear","Grape"]La fonction any vérifie si au moins un élément d'une liste satisfait un prédicat donné :
any :: (a -> Bool) -> [a] -> BoolExemple : vérifier si un élément de la liste est supérieur à 4 :
biggerThan4 x = x > 4
any biggerThan4 [1,2,3,4]
FalseUn exemple plus concret : vérifier si une liste de voitures en vente contient encore des modèles disponibles :
cars = [("Toyota",0), ("Nissan",3), ("Ford",1)]
biggerThan0 (_,x) = x > 0
any biggerThan0 cars
TrueUne fonction lambda (ou fonction anonyme) est une fonction qui n'a pas de nom.
Exemple :
\x y -> x * yLes fonctions lambda sont particulièrement utiles pour éviter de nommer des fonctions utilisées une seule fois.
Exemple avec any :
any (\x -> x > 4) [1,2,3,4]
FalseOu encore avec filter :
filter (\x -> 'a' `elem` x) ["Apple", "Banana", "Pear", "Grape", "Wood"]
["Banana","Pear","Grape"]Haskell attribue une priorité (de 0 à 9) aux opérateurs.
Exemple :
:i (+) -- infixl 6 +
:i (*) -- infixl 7 *Puisque * a une priorité plus haute que +, l'expression suivante :
1 + 2 * 3est évaluée comme 1 + (2 * 3), donnant 7.
L'associativité indique si un opérateur s'évalue de gauche à droite (infixl) ou de droite à gauche (infixr).
En Haskell, toutes les fonctions sont curryfiées, c'est-à-dire qu'elles prennent un seul argument et retournent une fonction qui attend les arguments restants.
Exemple :
add3 :: Int -> Int -> Int -> Int
add3 x y z = x + y + zest en réalité :
add3 :: Int -> (Int -> (Int -> Int))
add3 = \x -> \y -> \z -> x + y + zL'application partielle permet de fixer certains paramètres d'une fonction pour créer une nouvelle fonction.
Exemple :
createEmail :: String -> String -> String -> String
createEmail domain name lastName = name ++ "." ++ lastName ++ "@" ++ domain
createEmailTeckel = createEmail "teckel-owners.com"
createEmailSCL = createEmail "secret-cardano-lovers.com"
createEmailTeckel "Robertino" "Martinez"
"Robertino.Martinez@teckel-owners.com"L'opérateur $ réduit l'usage des parenthèses :
show $ (2**) $ max 3 $ 2 + 2équivaut à :
show ((2**) (max 3 (2 + 2)))L'opérateur . permet de composer des fonctions :
(complicatedF x) = any even . filter (>25) . tail . take 10 $ xCeci remplace plusieurs appels imbriqués et rend le code plus lisible.