Dans un premier temps, on écrit les fonctions dans un fichier .hs
qu’on charge avec :load
après avoir lancé le REPL en tapant ghci
.
On utilise deux sortes de définition: l’équation et la signature.
Les fonctions sont définies par une équation comme dans
inc n = n + 1
.
On peut aussi explicitement typer la fonction (signature) :
inc :: Integer -> Integer
plus généralement inc :: Num a => a -> a
En général, il n’est pas nécessaire de donner cette signature car elle est automatiquement déduite, mais c’est une forme de documentation utile.
Les équations sont en fait une manière élégante de définir des fonctions
inc x = x + 1
add x y = x + y
équivalente à l’utilisation de lambda-fonctions (fonctions anonymes, où la barre oblique rappelle le plus long des deux traits d’un lambda).
inc = \x -> x + 1
add = \x y -> x + y
En Haskell, il n’y a pas besoin de parenthèses pour appeler une fonction. Tous les arguments sont listés après la fonction.
Schéma général : func arg1 arg2 arg3 ...
Exemple 1 : func arg1 (add 2 5) arg ...
.
Le deuxième argument est add 2 5
.
Exemple 2 : func arg1 add 2 5 arg ...
.
Les arguments 2, 3, 4 sont respectivement add, 2
et 5
.
Les opérateurs binaires tels que :
ou ++
ne sont rien d’autre que des fonctions à deux paramètres.
Ce sont généralement des signes qui sont placés entre les deux arguments :
1 : [2,3]
. Mais on peut tout aussi bien les entourer de parenthèses pour les utiliser comme des fonctions :
(:) 1 [2,3]
.
Inversement, les fonctions peuvent être placées entre quotes penchées pour les utiliser comme des opérateurs infixes :
take 2 "abc"
est equivalent à 2 `take` "abc"
.
inc
et add
dans un fichier appelé ex.hs
.:load ex.hs
.add 2 5
3 `add` 6
4 + 7
(+) 4 7
inc 3
(add 1) 3
add (1 3)
Les équations peuvent contenir des gardes.
f arg = expr
,
l’appel f val
sera toujours evalué à expr
,f arg | cond = expr
,
l’appel f val
est évalué à expr
si et seulement si cond
est vrai,
où cond
est une expression booléenne impliquant l’argument, comme par exemple arg > 0
.Plusieurs gardes peuvent être chaînés. Dans ce cas, ils sont testés les uns après les autres.
sign x | x > 0 = 1 | x == 0 = 0 | otherwise = -1
sign x
| x > 0 = 1
| x == 0 = 0
| otherwise = -1
sont la traduction Haskell de :
Haskell utilise l’alignement en colonne pour accroître la lisibilité. Une ligne indentée plus à droite que la précédente est la suite de la ligne précédente.
Ci-dessous, il y a une erreur car aucune expression ne peut commencer par |
.
sign x
| x > 0 = 1
| x == 0 = 0
| otherwise = -1
2:1: parse error on input ‘|’
-- Un commentaire en une ligne commence avec deux tirets.
{- Un commentaire sur plusieurs lignes peut être contenu dans
un bloc de cette façon.
-}
Notez que dans les fichiers d’extension .lhs
, seules les lignes
précédées par >
et espace (Bird style) sont considérées comme
du code Haskell.
addElemInList
§A l’aide de gardes, définissez la fonction
addElemInList :: a -> Int -> [a] -> [a]
qui ajoute un élément donné, un nombre de fois donné, dans une liste donnée.
*Main> addElemInList 1 3 []
[1,1,1]
*Main> addElemInList 'a' 2 "bb"
"aabb"
*Main>
Une case-expression fournit le moyen de distinguer plusieurs cas selon une valeur et sa structure.
myLen :: [a] -> Integer
myLen lst = case lst of
[] -> 0
(x:xs) -> 1 + myLen xs
lst
est une liste vide, myLen lst
est évaluée en 0
,lst
est composé d’un élément x
en tête d’une liste xs
(possiblement vide)
myLen lst
est évaluée en 1 + myLen xs
.Tous les membres de gauche (devant les flèches) et tous les membres de droite (derrière) doivent avoir le même type,
respectement [a]
et Integer
dans cet exemple.
Dans une case-expression les membres de gauche sont des motifs (patterns) avec lesquels est mise en correspondance (matching) la valeur associée au paramètre.
Une mise en correspondance avec un motif peut soit
[1,2,3] != []
).
Dans ce cas, le motif suivant est essayé et si tout échoue, une erreur se produit ;[1,2,3] = 1:[2,3]
, correspondant au motif x:xs
où x=1
et xs=[2,3]
).
Dans ce cas, le membre de droite est évalué et retourné comme résultat de l’appel.dupli
§A l’aide d’une case expression, définissez la fonction
dupli :: [a] -> [a]
qui duplique les éléments d’une liste donnée.
*Main> dupli [1,2,3]
[1,1,2,2,3,3]
*Main> dupli "abc"
"aabbcc"
Dans un programme Haskell, une fonction est assez souvent définie par une série d’équations car
f x1 x2 ... xk = case (x1, ..., xk) of
(p11, ..., p1k) -> e1
...
(pn1, ..., pnk) -> en
est équivalent à
f p11 ... p1k = e1
...
f pn1 ... pnk = en
Mais une case expression peut être utilisée aussi dans une expression plus complexe.
compress
§Définissez la fonction
compress :: (Eq a) => [a] -> [a]
qui supprime les copies consécutives des éléments d’une liste.
*Main> compress "aaaabccaadeeee"
"abcade"
Parfois on a besoin de localement se référer à une valeur intermédiaire.
Une let-expression (let var = expr1 in expr2
) peut
être utilisée partout où une expression est requise.
(let x = 2 in x*2) + 3
Dans certains cas (REPL ou notation do
), un
let-statement (let var = expr
) peut
être utilisé.
Prelude> let x = 2
Prelude> x*2 + 3
7
where
§La clause where permet de partager une variable entre des parties d’une définition qui ne forme pas syntaxiquement une expression.
f x
| cond x = a
| otherwise = g a
where
a = w x
encode
§Définissez la fonction
encode :: (Eq a) => [a] -> [(Int, a)]
qui encode une liste donnée de façon à ce que toute suite
de n
éléments égaux à x
soit remplaçée par le tuple (n,x)
.
*Main> encode "aaaabccaadeeee"
[(4,'a'),(1,'b'),(2,'c'),(2,'a'),(1,'d'),(4,'e')]
Astuce : on suppose qu’on connaît le résultat pour une liste xs
.
Comment construire incrémentalement le résultat pour x:xs
?
.
(f (g x)
est équivalent à (f . g) x
).Le Prélude est un module doté d’un grand nombre de fonctions standards. Il est implicitement importé dans chaque programme Haskell.
length, head, tail, take, drop, init, last, reverse, elem
, etc.map
, filter
, foldl
, foldr
, foldl'
, etc.filter
§Un premier modèle de traitement consiste à ne conserver d’une liste que les éléments vérifiant une certaine condition.
filter :: (a -> Bool) -> [a] -> [a]
Le premier argument est un prédicat, c’est-à-dire une fonction qui
prend un a
et qui répond oui ou non. Le second argument est une
liste d’élément de type a
.
Tous les éléments pour lesquels la réponse est positive sont
dans la liste résultante (de taille plus petite ou égale).
Par exemple, l’expression filter (\x -> x < 10) [9,10,11,12]
est évaluée
à [9]
.
filter
§En utilisant les fonctions filter
et length
, donnez l’expression
qui retourne le nombre de ‘a’ dans la chaîne de caractère “aaaabccaadeeee”.
map
§Un deuxième modèle de traitement consiste à appliquer une même opération à tous les éléments d’une liste.
map :: (a -> b) -> [a] -> [b]
Le premier argument est une fonction qui prend un a
et retourne un b
.
Celle-ci est appliquée à tous les éléments de type a
d’une liste,
ce qui donne une liste d’éléments de type b
(de même taille).
En ayant défini inc x = x + 1
, l’expression map inc [1,2,3]
est évaluée
en [2,3,4]
.
add :: Integer -> Integer -> Integer
add x y = x + y
add
peut être évaluée avec deux arguments ou un seul,
dans ce dernier cas, il s’agit d’une application partielle.
add 2 3 :: Integer -- resultat de l'addition 2 + 3
add 1 :: Integer -> Integer -- fonction qui ajoute 1
Donc on peut aussi écrire map (add 1) [1,2,3]
En Haskell, une section est l’application partielle d’un opérateur infixe (qui s’écrit entre les deux opérandes). Par exemple :
(+)
\(\equiv\) \x y -> x+y
(x+)
\(\equiv\) \y -> x+y
(+y)
\(\equiv\) \x -> x+y
inc = (+1)
add = (+)
Donc on peut aussi écrire map (+1) [1,2,3]
map
§En utilisant la fonction addElemInList
et map
, donnez l’expression qui
transforme le codage [(4,'a'),(1,'b'),(2,'c'),(2,'a'),(1,'d'),(4,'e')]
en
["aaaa","b","cc","aa","d","eeee"]
.
Il existe une syntaxe particulière permettant de définir des listes en intention (appelée list comprehension)
et qui remplace avantageusement l’utilisation de map
et filter
dans un grand nombre de cas.
[ f x | x <- xs, x < k ]
signifie “la liste de tous les f x
telle que x
vienne de la liste xs
et soit inférieur à k
”
et s’inspire de la notation mathématique
foldr
and co.§Un troisième modèle de traitement consiste à combiner les éléments d’une liste.
Il est connu sous le nom fold (ou reduce dans d’autres langages). Il y a plusieurs variantes en Haskell : foldr, foldl or foldl’
A partir d’une fonction binaire f
et d’une valeur initiale z
,
les fonctions foldr
et foldl
vont combiner les valeurs d’une liste,
en partant de la droite (r pour right) ou de la gauche (l pour left).
foldr f z [a,b,c]
\(\equiv\) a `f` (b `f` (c `f` z))
foldl f z [a,b,c]
\(\equiv\) ((z `f` a) `f` b) `f` c
foldl'
est une variante plus efficace de foldl
foldr
en détail§foldr :: (a -> b -> b) -> b -> [a] -> b
La premier argument est une fonction qui combine un a
et un b
et retourne une valeur de type b
.
Le deuxième est une valeur initiale de type b
. Le troisième une liste de a
.
La fonction de combinaison va être d’abord appliquée à la valeur initiale z
et à l’élément de fin de liste (le plus à droite).
Le résultat sera ensuite combiné avec l’élément précédent et ainsi de suite.
La dernière application de la fonction de combinaison retournera la valeur finale.
foldr
§En utilisant la fonction foldr
, donnez l’expression qui transforme la liste
["aaaa","b","cc","aa","d","eeee"]
en la chaîne "aaaabccaadeeee"
Grâce à l’application partielle, comme pour inc = (+1)
,
il est possible de définir mySum
sans donner aucun argument :
mySum :: [Integer] -> Integer
mySum = foldr (+) 0
au lieu de
mySum lst = foldr (+) 0 lst
C’est le “point-free style” (où “point” réfère aux arguments) qu’adopte les développeurs expérimentés parce qu’il décrit ce qu’est une fonction, plutôt que ce qu’elle fait.
Comme dans le Prélude, définissez les fonctions suivantes à l’aide de foldr
:
length :: [a] -> Int
sum :: Num a => [a] -> a
product :: Num a => [a] -> a
and :: [Bool] -> Bool
or :: [Bool] -> Bool
any :: (a -> Bool) -> [a] -> Bool
all :: (a -> Bool) -> [a] -> Bool
filter
, map
, foldr
ou foldl
, etc.addElemInList
§addElemInList :: a -> Int -> [a] -> [a]
addElemInList x c lst
| c <= 0 = lst
| c > 0 = addElemInList x (c-1) (x:lst)
dupli
§dupli :: [a] -> [a]
dupli lst = case lst of
(x:xs) -> x:x:(dupli xs)
[] -> []
compress
§compress :: (Eq a) => [a] -> [a]
compress [] = []
compress [x] = [x]
compress (x:y:xs)
| x == y = compress (y:xs)
| otherwise = x:(compress (y:xs))
encode
§encode :: (Eq a) => [a] -> [(Int, a)]
encode [] = []
encode (x:xs) = case res of
[] -> [(1,x)]
( (nb,elem) : ys)
| x == elem -> (nb+1,x):(ys)
| otherwise -> (1,x):res
where res = encode xs
filter
§Prelude> length (filter (\x -> x == 'a') "aaaabccaadeeee")
6
Prelude> ( length . filter (\x -> x == 'a') ) "aaaabccaadeeee"
6
map
§*Main> :l addElemInList.hs
[1 of 1] Compiling Main ( addElemInList.hs, interpreted )
Ok, modules loaded: Main.
*Main> let f t = case t of (nb, elem) -> addElemInList elem nb []
*Main> map f [(4,'a'),(1,'b'),(2,'c'),(2,'a'),(1,'d'),(4,'e')]
["aaaa","b","cc","aa","d","eeee"]
foldr
§Prelude> foldr (++) [] ["aaaa","b","cc","aa","d","eeee"]
"aaaabccaadeeee"
import Prelude hiding (length, sum, product, and, or, any, all)
--on cache les fonctions existantes pour les redéfinir
length :: [a] -> Int
length = foldr (\_ y -> y+1) 0
sum :: Num a => [a] -> a
sum = foldr (+) 0
product :: Num a => [a] -> a
product = foldr (*) 1
and :: [Bool] -> Bool
and = foldr (&&) True
or :: [Bool] -> Bool
or = foldr (||) False
any :: (a -> Bool) -> [a] -> Bool
any p = foldr (\x y -> (p x) || y) False
all :: (a -> Bool) -> [a] -> Bool
all p = foldr (\x y -> (p x) && y) True