Introduction au langage Haskell
Voir aussi : vidéo peertube - vidéo youtube - dépôt git
Généralités sur haskell
- programmation fonctionnelle : imbrication de fonctions sans effet de bord
- intérêts : sûreté, expressivité, optimisation du compilateur
- utilisation : compilation, DSL, web backend…
- particularités d’Haskell : fonctionnel pur, évaluation paresseuse
- quelques liens :
Notions de bases
Quelques définitions
- valeur : donnée de base
Prelude> 42
42
Prelude> 13.37
13.37
Prelude> "toto"
"toto"
- expression : morceau de code pouvant être évalué
Prelude> 21*2
42
Prelude> "hello" ++ "world"
"helloworld"
- type : ensemble prédéfini de valeurs possibles avec des caractéristiques et opérations particulières
Prelude> 21*2 :: Int
42
Prelude> "toto" :: String
"toto"
- variable : nom auquel on associe une donnée
Prelude> x = 12
Prelude> y = "toto" :: String
Prelude> x*2
24
Prelude> y ++ y
"totototo"
Quelques remarques
En Haskell, les variables sont immuables et les expressions respectent la propriété de transparence référentielle
Haskell calcule les types automatiquement mais on peut l’indiquer (pour plus de lisibilité)
Haskell est sensible à l’indentation
les variables et les fonctions commencent par une lettre minuscule
les types et les classes de type commencent par une lettre majuscule
les parenthèses servent à définir des priorités d’évaluation
opérateur d’évaluation
$
commentaire de fin de ligne :
= 42 -- ceci est un commentaire x
- commentaire multi-ligne :
{-
ceci est
un commentaire
-}
Types de base
- types simples :
Int
,Float
,String
…
x :: Int
= 42
x
y :: String
= "toto" y
- types composés : tuples, listes
x :: (Int, String)
= (42, "toto")
x
y :: [Int]
= [42, 13, 37] y
Notion de fonction
- fonction : expression contenant des paramètres; définition/évaluation
-- définit une fonction "ajouter"
ajouter :: Int -> Int -> Int
= x + y
ajouter x y
-- évalue la fonction "ajouter" pour les paramètres 10 et 32
x :: Int
= ajouter 10 32 x
- fonction anonyme (lambda)
-- définit une lambda et l'évalue pour le paramètre 32
x :: Int
= (\ n -> 10+n) 32 x
Définir des fonctions par décomposition
if-then-else
: définit une expression en décomposant 2 cas
formaterParite :: Int -> String
= if even x then "pair" else "impair" formaterParite x
case
: définit une expression en décomposant plusieurs cas
formaterNombre :: Int -> String
= case x of
formaterNombre x 0 -> "zero"
1 -> "un"
otherwise -> "plusieurs"
- pattern matching : définit une fonction selon la valeur des paramètres
formaterNombre :: Int -> String
0 = "zero"
formaterNombre 1 = "un"
formaterNombre = "plusieurs" formaterNombre _
- gardes : définit une fonction en testant ses paramètres
formaterNombre' :: Int -> String
formaterNombre' x| x == 0 = "zero"
| x < 0 = "negatif"
| otherwise = "positif"
Récursivité
- fonction récursive : fonction définie d’après elle-même
factorielle :: Int -> Int
1 = 1 -- cas terminal
factorielle = x * factorielle (x - 1) -- appel récursif factorielle x
- fonction récursive terminale : l’appel récursif correspond complètement à la dernière expression
factorielle :: Int -> Int
= aux 1 x
factorielle x where aux acc 1 = acc -- fonction auxiliaire récursive terminale
= aux (acc*n) (n-1) aux acc n
Fonction d’ordre supérieur
- définition : fonction qui retourne une fonction
ajouter :: Int -> Int -> Int
= \ y -> x + y
ajouter x
-- autre façon de définir :
-- ajouter x y = x + y
- évaluation partielle : évaluation d’une fonction pour une partie des paramètres; produit une fonction
ajouter42 :: Int -> Int
= ajouter 42 -- ici seul le paramètre "x" de la fonction "ajouter" est donné
ajouter42
-- autre façon de définir ajouter42 :
= ajouter 42 y ajouter42 y
- composition de fonctions : définit une fonction en combinant deux autres fonctions
= x*2
fois2 x = x+1
plus1 x = plus1 . fois2
fois2plus1
-- autre façon de définir fois2plus1 :
-- fois2plus1 x = plus1 (fois2 x)
- notation “point-free” : définit une fonction en combinant d’autres fonctions plutôt qu’en exprimant le calcul sur un point du domaine de définition
= plus1 (fois2 x) -- calcul sur un point "x" du domaine
fois2plus1 x = plus1 . fois2 -- notation point-free
fois2plus1
= ajouter 42 y -- calcul sur un point "y"
ajouter42 y = ajouter 42 -- notation point-free ajouter42
Fonctions sur des listes
- opérateur de construction de liste, par exemple
(:) :: Int -> [Int] -> [Int]
Prelude> 42:[13,37]
42,13,37] [
- pattern matching + récursivité
doubler :: [Int] -> [Int]
= []
doubler [] :xs) = (2*x):(doubler xs) doubler (x
- mapping : applique une fonction sur chaque élément d’une liste
doubler :: [Int] -> [Int]
= map (\ x -> x*2)
doubler
-- encore plus concis :
-- doubler = map (*2)
- filtrage : conserve les éléments d’une liste qui vérifient un prédicat
pairs :: [Int] -> [Int]
= filter (\ x -> even x)
pairs
-- encore plus concis :
-- pairs = filter even
- réduction : réduit une liste en une valeur en appliquant successivement une fonction sur les éléments
somme :: [Int] -> Int
= foldl (\ acc x -> acc + x) 0
somme
-- encore plus concis :
-- somme = foldl (+) 0
Listes en compréhension
- définition de liste combinant génération + filtrage + mapping
Prelude> [(x,y) | x<-[1..10], y<-[x..10], x+y==12]
2,10),(3,9),(4,8),(5,7),(6,6)] [(
Modules
- exemple de définition (fichier
Doubler.hs
) :
module Doubler where
= x * 2 doubler x
- exemple d’utilisation :
import Doubler
= doubler 21 x
Types et classes
Type algébriques
- définir un nouveau type :
data Forme = Carre Float
| Rectangle Float Float
- créer des valeurs du nouveau type :
= Carre 2.0
monCarre = Rectangle 2.0 0.5 monRectangle
- écrire une fonction avec pattern matching sur les valeurs du type :
calculerSurface :: Forme -> Float
Carre cote) = cote*cote
calculerSurface (Rectangle longueur largeur) = longueur*largeur calculerSurface (
- utilisation de la fonction :
= calculerSurface monCarre
s1 = calculerSurface monRectangle s2
Type paramétrique
- typage paramétrique :
renverser :: [a] -> [a]
= foldl (\ acc x -> x:acc) [] renverser
- utilisation :
= renverser [1..5]
l1 = renverser "hello" l2
Classes de type
- indiquer une contrainte de type :
doubler :: Num a => a -> a
= x*2 doubler x
- utilisation avec différents types de la classe :
n1 :: Int
= doubler 21
n1
n2 :: Float
= doubler 1.2 n2
Classes prédéfinies
classe | fonctions de la classe | types de la classe |
---|---|---|
Eq |
== /= |
Int Float String … |
Show |
show |
Int Float String … |
Num |
+ - * negate abs signum |
Int Float … |
Instances de classe
- implémenter les fonctions d’une classe pour un type :
instance Show Forme where
show (Carre c) = "carre de côté " ++ (show c)
show (Rectangle w h) = "rectangle de longueur " ++ (show w) ++ " et de largeur " ++ show h
- utilisation :
= do
main print (Carre 12)
print (Rectangle 12 3)
Définir une nouvelle classe
- définir une classe et ses fonctions à implémenter :
class Surfacable a where
surface :: a -> Float
- utilisation :
instance Surfacable Forme where
= calculerSurface
surface
surfaces :: Surfacable a => [a] -> [Float]
= map surface
surfaces
= do
main print $ surfaces [Carre 12, Rectangle 12 3]
Entrées/sorties avec IO
- fonction
main
:
main :: IO ()
= print 42 main
- notation
do
:
main :: IO ()
= do
main putStrLn "hello"
print 42
- récupérer une entrée :
= do
main putStrLn "entrez un texte : "
<- getLine
ligne putStrLn ligne
Monades
design pattern modélisant la notion de séquence et de composition
implémenté par une classe définissant les opérateurs
>>=
(bind),>>
(then) etreturn
la notation
do
un raccourci syntaxiqueexemples de monades :
IO
,Maybe
…
renverser :: String -> IO String
= return . reverse
renverser
-- avec la notation do
= do
main putStrLn "entrez un texte : "
<- getLine
ligne <- renverser ligne
engil putStrLn engil
-- avec les opérateurs
= putStrLn "entrez un texte : " >> getLine >>= renverser >>= putStrLn main