Le langage Haskell

 

1) Introduction

Son nom vient du mathématicien Haskell Brooks Curry. Haskell est un langage fonctionnel, c'est-à-dire que tout est fait avec des appels de fonction. Il est typé statiquement et implicitement, les types sont vérifiés par le compilateur et il n'est pas nécessaire de les déclarer. Il est paresseux, rien n'est fait avant que ce ne soit nécessaire.

1.1) Le compilateur GHC

GHC (Glasgow Haskell Compiler) est un compilateur libre pour le langage Haskell développé par l'Université de Glasgow sous licence BSD. Pour le télécharger, aller sur le site http://www.haskell.org/

1.2) L'interprèteur GHCi

Il accompagne le compilateur GHC, et permet d'exécuter à la volée des instructions haskell. C'est un interpréteur. Il permet également de lancer des commandes systèmes, de chargement de fichiers, d'édition, d'information, de contrôle, de débugage... Il peut charger des modules compilés ou interprétés.

Pour le lancer dans une fenêtre MSDOS, exécuter GHCi.

WinGHCi est la version graphique de GHCi.

On peut Spécifier l'éditeur que l'on souhaite utiliser (ici l'éditeur Notepad++) en lanceant la commande suivante :
      File|Option Editor : C:\Program Files\SciTE\Notepad++.exe
ou la ligne de commande suivante :
   :set editor C:\Program Files\SciTE\Notepad++.exe

1.3) L'interprèteur Hugs

Hugs est un interpréteur bytecode. Il offre une compilation rapide du Haskell en bytecode et une vitesse d'exécution raisonnable. Il possède aussi une bibliothèque graphique simple. Il permet de lancer des commandes système comme dans l'interpreteur GHCi. Hugs se prête bien à l'apprentissage de Haskell, mais il ne faut pas en déduire que Hugs est une implémentation simpliste. C'est la plus portable et la plus légère des implémentations de Haskell. Pour télécharger l'interpréteur Hugs, aller sur le site http://cvs.haskell.org/Hugs/.

Pour lancer l'interpreteur Hugs dans une fenêtre MSDOS, exécuter la commande "C:\Program Files\WinHugs\hugs.exe".

WinHugs est la version graphique de Hugs.

1.4) Editeur Notepad++

Notpad++ est un éditeur de code source qui prend en charge plusieurs langages dont haskell. Il donne une coloration syntaxique pour de nombreux langages. Pour le télécharger, aller sur le site http://notepad-plus-plus.org/fr/

1.5) Editeur SciTE

SciTE est un éditeur de code source qui prend en charge plusieurs langages. Il donne une coloration syntaxique pour de nombreux langages, et on peut facilement en ajouter d'autres. Pour le télécharger, aller sur le site http://www.scintilla.org/
La partie droite de l'éditeur se comporte comme une fenêtre de commande MSDOS
Pour ajouter le langage Haskell à sa liste de langage, faite comme suit :
   1- Copier le fichier haskell.properties dans le répertoire C:\Program Files\SciTE\
   2- Modifier le fichier SciTEGlobal.propertie (ouvrable dans SciTE via Option|Open Global Options File) comme suit :
       Ajoutez-y l'extention hs dans la commande suivante :
             source.files=*.asm;*.c;*.cc;*.cpp;*.cxx;*.cs;*.h;*.hs;*.hh;*.hxx;*.hp
    
Ajoutez-y le langage Haskell dans la commande "menu.language=" entre le langage Gap et Hypertext comme suit :
             ...
       #Gap|g||\
       &Haskell|hs||\
       H&ypertext|html|F12|\
         ...
        Ajoutez-y le langage Haskell dans la liste des imports entre le langage Gap et Hypertext comme suit :
         ...
         #import gap
       import haskell
       import html
       ...

2) Compiler un programme Haskell

Créez le fichier hello.hs et placez-y le programme Haskell suivant :

main = putStrLn "Bonjour"

Compilez-le avec la commande

ghc -o hello hello.hs

Cela crée dans le même répertoire le fichier : hello.o, hello.hi, et le fichier exécutable hello.exe

3) Documents sur le langage Haskell

Documents PDF :

Tutoriaux en français
I) Une introduction agréable au langage Haskell 98
Paul Hudak (Yale University),
John Peterson (Yale University)
et Joseph Fasel (Los Alamos National Laboratory)
Traduction par: Nicolas Vallée - 2007


II) Programmation fonctionnelle (Haskell)
Wim Vanhoof - 2004

III) Tutorial Haskell pour le développeur C
Eric Etheridge
Traduction par Corentin Dupont - 2008

IV) 64 exercices de programmation en Haskell
L.Gacôgne - 2001
Tutoriaux en anglais
I) Haskell
From Wikibooks - 2007

II) Yet Another Haskell Tutorial
Hal Daumé III - 2002-2006

III) The Haskell Road to Logic, Math and Programming
Kees Doets and Jan van Eijck - 2004

IV) Haskell-Tutorial
Damir Medak, Gerhard Navratil - 2003
Institute for Geoinformation Technical University Vienna


V) Learn You a Haskell for Great Good!
Miran Lipovaca

VI) Beginning with the Haskell Programming Language
Histoire
A History of Haskell : Being LazyWith Class - 2007
Paul Hudak (Yale University)
John Hughes (Chalmers University)
Simon Peyton Jones (Microsoft Research)
Philip Wadler (University of Edinburgh)
Les IO-Monades
The Haskell Programmer’s Guide to the IO Monad
Stefan Klinger (University of Twente, the Netherlands) - 2005
Le paradoxe de la pureté en programmation fonctionnelle : les monades salvatrices
Michaël Monerau - 2009
Présentations en français
Plaquettes en anglaise
1) Haskell Quick Reference COSC 8—Fall 2007
Based on Chapter 23 of "The Haskell School of Expression"

2) A tour of the Haskell Prelude
Bernie Pope - 2001

3) Haskell Cheat Sheet
Justin Bailey - 2009

4) Haskell Cheat Sheet
Christopher MANEU
Documents avancés
Haskell 98 Language and Libraries. The Revised Report
Simon Peyton Jones

Foreign Function Interface 1.0 An Addendum to the Haskell 98 Report
Manuel Chakravarty (University of New South Wales)
Sigbjorn Finne (Galois Connections, Inc)
Fergus Henderson (University of Melbourne)
Marcin Kowalczyk (Warsaw University)
Daan Leijen (University of Utrecht)
Simon Marlow (Microsoft Research, Cambridge)
Erik Meijer (Microsoft Corporation)
Sven Panne (BetaResearch GmbH)
Simon Peyton Jones (Microsoft Research, Cambridge)
Alastair Reid (Reid Consulting (UK) Ltd)
Malcolm Wallace (University of York)
Michael Weber (University of Aachen)
Les classe de types

Classes de types de première classe
Matthieu Sozeau (Univ. Paris Sud, CNRS, Laboratoire LRI,
INRIA Saclay, ProVal, Parc Orsay Université)
Nicolas Oury (Université de Nottingham),

Documents en ligne :

http://onlamp.com/pub/a/onlamp/2007/05/21/an-introduction-to-haskell---part-1-why-haskell.html
Adam Turoff - 2007
Traduction

http://progmod.org/tutoriel/2/programmez-en-haskell/  Site du Zéro
gnomnain

http://litpc9.ulb.ac.be/lang/index.php/Haskell
Stefan Langerman, Frédéric Pluquet

Haskell 98 Language and Libraries, The Revised Report
www.haskell.org - 2002

Real World Haskell
Bryan O'Sullivan, Don Stewart, and John Goerzen - 2007-2008

http://en.wikibooks.org/wiki/Haskell
en.Wikibooks.org

http://cs.fit.edu/~ryan/cse4250/haskell.html
Stansifer, Ryan.

3) Découverte du langage Haskell

Quel but recherchons nous dans une tel documentation ? le but est, dans le cadre de la démarche d'appréhendement du langage Haskell (autrement dit dans le cadre de son apprentissage) par des programmeurs déja au faite des méthodes de programmation impérative, object, fonctionnelle et logique, de trouver rapidement et avec le moins d'effort possible, une instruction haskell résolvant une étape de progrrammation nécessaire à la résolution d'un problèmes plus vaste. (voir §10.3 La documentation et le langage ).

L'expérience nous montre combien la synoptique par l'exemple est plus pratique que celle par définition, et que les conventions de nommage propre à la documentation facilitent son utilisation sans imposer de containte supplémentaire au language.

L'ordre dans lequel est présenté le langage, comme un jeu de légos, présentant en premier les pièces les plus simples et les plus génériques, pour montrer en second les différentes constructions que l'on peut faire avec, donne une assurance à celui qui apprend, ainsi que la capacité de créer par lui-même et donc de s'automotiver.

3.1) Valeurs et Types

Dans Haskell, les valeurs correspondent à des éléments, et les types correspondent à des ensembles d'éléments. Il existe des variables contenant des valeurs et des variables de type contenant des types. Ces variables sont logique, c'est à dire qu'elle ne subissent pas d'affectation autre que leur déclaration initiale mais parcontre sont soumisent à des unifications, et sont relatives à une partie du code tel les paramètres d'entrés d'une fonction appellés communément variables muettes.

Les valeurs peuvent être passés à des fonctions en tant qu'arguments, retournées en tant que résultats, placées dans des listes ou des tuples ou dans la tête et le corps d'une fonction, par contre il n'en est pas de même pour les types.

Les identifants se compose de minuscules, majuscules, chiffres, du caractère souligné _ et du caractère prime '.

Les variables contiennent des valeurs, et leur nom commencent par une minuscule (ou par le caractère '_'). Et dans la documentation les variables sont notées de couleur bleu.

Les types nominaux commencent par une majuscule. Dans la documentation les types sont notés en gras.

Les variables de type contiennent des types, et leur nom commence par une minuscule (ou par le caractère '_'). Et dans la documentation les variables de type sont notés en rouge.

3.2) Les types simples

On considère différents types simples et pour chacun de ces types, on se donne des variables génériques possèdant implicitement ce type. Cela évite de redéclarer le type des variables au cours de la documentation et de se perdre dans des détails qui sont posés une fois pour toutes.

Type simple
Description
Exemple
Variables génériques pour la documentation
Char
Les caractères
'a'
c
Int
Les entiers signés sur 32 bits
120
i
Integer
Les entiers signés
120
nm
Double
Les nombres à virgule
3.14
xy
Bool
Les valeurs booléennes
True
b

3.3) Les trois procédés de construction d'éléments : liste, tuple, fonction

On considère les trois procédés de construction d'éléments que sont les listes, les tuples et les fontions.

Lorsque nous étendons le langage d'élément pour construire de nouveaux éléments que sont les listes, les tuples et les fonctions, nous étendons également le langage des types permettant de construire de nouveaux types que sont les différents types de listes, les différents types de tuples et les différents types de fonctions.

3.4) Les types de liste

Le type de liste le plus générale est noté [a]a joue le rôle d'une variable de type sur laquelle il n'y a ici aucune condition. Puis pour chaque types on peut définir le type de liste d'éléments de ce type comme par exemple :

Type
Description
Exemples d'éléments
[a]
Type des listes [True,True],  [[],[5]]"Bonjour"[1,2,3]
[Integer]
Type des listes d'entiers [1,2,3]
[Char]
Type des listes de caractères = Type String "Bonjour"
[Bool]
Type des listes de booléens [True,True,False]
[[a]]
Type des listes de listes [[],[5]]["Hello","By"]
[[Bool]]
Type des listes de listes de booléens [[True,True],[False],[]]
[(a,b)]
Type des listes des couples [(5,'a'),(2,'b')][(True,[1]),(False,[2])][(1,2)]

3.5) Les types de tuple

Un couple est un tuple de taille 2, et un triplet est un tuple de taille 3. Le type de couple le plus générale est noté (a,b)a et b jouent le rôle de variables de type sur lesquelles il n'y a ici aucune restriction. Puis on peut restreindre ces variables de type a et b en les unifiants à d'autre types :

Type
Description
Exemples d'éléments
(a,b)
Type des couples (5,'a'),  ([2],(5,'a'))((),[])(True, "Hello")
(Integer,a)
Type des couples d'un entiers et d'un éléments (5,'a')(8,False)(1,2), (2,"Bonjour")(3,[1,2])
(Integer, Bool)
Type des couples d'un entiers et d'un booléen (8, False)(0, True)(1,True)
([a],a)
Type des couples d'une liste et d'un élément de même type que ceux de la liste ([1,2],3)("Bonjour",'a')([True], False)([],1)

3.6) Les types de fonction

Une fonction f possède un type a d'éléments de départ et un type b d'éléments d'arrivé, et on dit alors que f possède le type a->b. Une fonction la plus générale est de type a->ba et b jouent le rôle de variables de type sur lesquelles il n'y a ici aucune condition. a représente l'ensemble de départ et b l'ensemble d'arrivé.

Type
Description
Exemple d'élément
a->b.
Type des fonctions \x->[x,x*x]
Int->Int
Type des fonctions de Int vers Int \x->x+1
Char->Bool
Type des fonction de Char vers Bool \c->c=='a'
(Int,Int)->Int
Type des fonction de (Int,Int) vers Int \(x,y)->x*y
[Int,Int]->Int
Type des fonction de [Int,Int] vers Int \[x,y]->x*y
Int ->(Int->Int)
Type des fonction de Int vers Int->Int \x->(\y->x*y)
(Int->Int)->Int
Type des fonction de Int->Int vers Int \f->f x

Par défaut le module Prélude ne prévoit pas d'afficher les éléments qui sont des fonctions, et donc dans l'interpréteur, lorsque on retourne une valeur qui est une fonction, celui-ci génère une erreur : No instance for (Show ...). Cela étant, on pourrait tout à fait concevoir une méthode d'affichage. Les variables, les listes et les tuples peuvent contenir des fonctions, mais alors elles ne peuvent pas s'afficher dans l'interprèteur.

3.7) Les variables génériques pour la documentation

On complète la liste des variables génériques pour la documentation :

Type simple
Description
Exemple
Variables génériques pour la documentation
[a]
Les listes
[1,2,3]"abc"
ls
(a,b)
Les couples
(5, True)
u
a->b
Les fonctions
\x->x*x
fgh
a->Bool
Les fonctions caractristiques
\x->even x
pq

3.8) Les opérateurs

Opérateur
Appellation
Appel
Sense (())
Priorité
 
Application
f x
gauche
10
!!
Indexage
l!!n
gauche
9
.
Composition
f.g
droite
9
^
Exponentiation
x^n
droite
8
**
Exponentiation
x^y
droite
8
*
Mulptiplication
x*y
gauche
7
/
Division
x/y
gauche
7
`div`
Division entière
n`div`m
gauche
7
`mod`
Modulo
n`mod`m
gauche
7
+
Plus
x+y
gauche
6
-
Moins
x-y
gauche
6
:
Construction de liste
x:l
droite
5
++
Concaténation
l1++l2
droite
5
/=
Différent
x/=y
aucun
4
==
Egale
x==y
aucun
4
<
Inférieur
x<y
aucun
4
<=
Inférieur ou égale
x<=y
aucun
4
>
Supérieur
x>y
aucun
4
>=
Supérieur ou égale
x>=y
aucun
4
`elem`
Appartient
x`elem`l
aucun
4
`notElem`
N'appartient pas
x`notElem`l
aucun
4
&&
Et
b1&&b2
droite
3
||
Ou
b1||b2
droite
3

L'application s'évalue par la gauche. Cela signifie que : x y z t = ((x y) z ) t
La composition s'évalue par la droite. Cela signifie que : x.y.z.t = x (y (z t))

Chaque opérateur infixe, tel que 5 * 6 admettent une notation préfixe (*) 5 6
Chaque opérateur préfixe, tel que f 5 6 admettent une notation infixe 5 `f` 6

3.9) Opérations de base

Commande
Appellation
Appel
Description
Exemples
Résultat
head
Tête
head s
Premier élément de la liste s head[1,2,3] => 1
tail
Queue
tail s
Queue de la liste s tail[1,2,3] => [2,3]
last
Dernier
last s
Dernier élément de la liste s last[1,2,3] => 3
:
Paire pointée
e : s
Construction de liste en ajoutant l'élément e à la liste s 1:[2,3] => [1,2,3]
length
Longueur
length s
Longueur de la liste s length[5,2,9] => 3
++
Concaténation
s1 ++ s2
Concaténation des listes s1 et s2 [1,2]++[3,4] => [1,2,3,4]
elem
Appartenance
elem e s
Si e appartient à la liste s elem 6 [5,6,5]

=> True

[ .. ]
Enumération
[x..y]
Liste des éléments consécutifs de x à y [2..5] => [2,3,4,5]
take
Premiers
take n s
Liste des n premiers éléments de la liste s take 2 [0,1,2,3] => [0,1]
!!
N-ième
s !! n
n-ième élément de la liste s en partant de 0 [5,6,7,8]!!2 => 7
sum
Somme
sum s
Somme des éléments de la liste s sum[2,10,2] => 14
product
Produit
product s
Produit des éléments de la liste s product[2,3,3] => 18
reverse
Reverse
reverse s
Liste s de sense inversé reverse[5,6,7] => [7,6,5]


Commande
Appellation
Appel
Description
Exemples
Résultat
fst
Premier
fst u
Premier élément du couple u fst(1,2) => 1
snd
Second
snd u
Premier élément du couple u snd(1,2) => 2
zip
Zip
zip s1 s2
Liste des couples de même rang zip[1,2] [5,6] => [(1,5),(2,6)]
unzip
Unzip
unzip s
Couple de liste à partir d'une liste des couples unzip[(1,5),(2,6)] => ([1,2],[5,6])
zipWith
ZipWith
zipWith(op) s1 s2
Liste des opérations de même rang zipWith (+)[(1,5),(2,6)] => (1+2,5+6)

Exemple de produit et filtrage

[x|x<-l,f x]
[x|x<-[1..5],even x]
=> [2,4]
[(x,y)|x<-l1,y<-l2]
[(x,y)|x<-[1,2],y<-[1,2,3]]
=> [(1,5),(2,6)]

... n'est évalué que ce qu'il faut ainsi : take 5 [x | x <- [1..], odd x] => [1,3,5,7,9]
[(x, y) | x <- [1,2], y <- [1,2,3]] => [(1,1),(1,2),(1,3),(2,1),(2,2),(2,3)]

Commande
Appellation
Appel
Description
Exemples
Résultat
takeWhile
Filtre d'enmagasinage
takeWhile p s
Liste les premiers éléments succécifs de s tant qu'il satisfont p takeWhile odd [1,3,2,5] => [1,3]
dropWhile
Filtre de retrait
dropWhile p s
Liste les premiers éléments succécifsde s tant qu'il satisfont non p takeWhile odd [2,4,1,6] => [2,4]
map
map
map f s
Application de f au éléments de la liste s map (\x->x+1) [1,2,3] => [2,3,4]
foldl
fold left
foldl f e s
Itère l'opérateur binaires f par la gauche sur la liste s avec un élément initial e foldl (*) 5 [1,2,3,4] => 120 -- =(((5*1)*2)*3)*4
foldr
fold rigth
foldr f e s
Itère l'opérateur binaires f par la droite sur la liste s avec un élément initial e foldr (*) 5 [1,2,3,4] => 120 -- =1*(2*(3*(4*5)))
until
Jusqu'à ce que
until p f x
calule f(f(..f(x)..) jusqu'à ce que le résultat satisfasse p. until (>100) (*2) 5 => 160

 

Commande
Appellation
Appel
Description
Exemples
Résultat
and
ET
and s
Effectue un ET logique sur les éléments d'une liste s and [True,False,True] => False
or
OU
or s
Effectue un OU logique sur les éléments d'une liste s or [True,False,True] => True
all
Tous
all p s
Teste si tous les éléments de s satisfont p all odd [1,3,7] => True
any
Au moin un
any p s
Teste si il existe un élément de s satisfaisant p any odd [2,2,3,4] => True

 

Commande
Appellation
Appel
Description
Exemples
Résultat
iterate
 
iterate f e
[e, f(e), f(f(e)), f(f(fe)))...] take 8 (iterate (*2) 1) => [1,2,4,8,16,32,64,128]
[..]
 
[x..]
[x, x+1, x+2, x+3...] take 3 [1..] => [1,2,3]
 
[x..y]
x est les succésseurs jusqu'à y [4..6] => [4,5,6]
   
[x,y..]
[x, y, x+2*(y-x), x+3*(y-x)...] take 4 [0,2..] => [0,2,4,6]
 
[x,y..z]
x est les suivants de pas (y-x) jusqu'à z [9,6..0] => [9,6,3,0]

r = 0:r
r!!36 => 0

3+5
2**64
2^^64
2^64
11/3
div 104 5
mod 104 5
divMod 104 5
sqrt 2
pi
exp 3
0xff
0o77
'a'
"Bonjour"
reverse "Bonjour"

=> 8
=> 1.8446744073709552e19
=> 1.8446744073709552e19
=> 18446744073709551616
=> 3.6666666666666665
=> 20
=> 4
=> (20,4)
=> 1.4142135623730951
=> 3.141592653589793
=> 20.085536923187668
=> 255
=> 63
=> 'a'
=> "Bonjour"
=> "ruojnoB"

L'opérateur :: sert à spécifier le type, et à le vérifier donc au moment de la compilation. Ainsi x :: Integer retourne x si x peut bien être restreint au type Integer sinon cela déclenche une erreur lors de la compilation. Voici quelques types :

Integer : type des entiers
Int : type des entiers signés sur 32 bits.
Char : type des charactères
[Integer] : type des listes d'entiers
(Char,Integer) : type des couples d'un charactère et d'un entier.

5 :: Integer
'a' :: Char
[2,5,4] :: [Integer]
('b',5) :: (Char,Integer)
[] :: [Integer]
"Bonjour" :: [Char]

=> 5
=> 'a'
=> [2,5,4]
=> ('b',5)
=> []
=> "Bonjour"

succ : la fonction successeur

succ 2
succ(2)
succ(succ 2)

=> 3
=> 3
=> 4

(succ :: Integer->Integer) 3
(succ :: Integer->Integer)(3::Integer)
(succ :: Int->Int)(3::Int)

=> 4
=> 4
=> 4

(succ :: Int->Int)((succ :: Int->Int)(3)

=> 5

3.9) Définition de fonction

Définition directe
f n = if n==0 then 1 else n*f(n-1)
Définition avec équations
f 0 = 1
f n = n*f(n-1)
Définition avec fonction de cas
f n = case n of
     0->1
     x->x*f(x-1)
Définition avec gardes
f n | n==0 = 1
    | otherwise = n*f(n-1)

 

f :: (a,b) -> c
(curry f) :: a->b->c

g :: a->b->c
(uncurry g) :: (a,b) -> c

 

 

head et tail peuvent être redéfinis par :
     head (x:_) = x
     tail (_:s) = s

map peut être redéfini par :
     map _ [] = []
     map f (x:q) = f x : map f q

let sum = foldl (+) 0; aplat = foldl (++) []
sum [1,2,3,4,5,6]    => 21
aplat [[1,2],[5,6],[9]]    => [1,2,5,6,9]

until peut être redéfini par :
     until p f x = if p x then x else until p f (f x)

map peut être redéfini par :
      map _ [] = [] et map f (x:q) = f x : map f q

all et any peuvent être redéfinie par :
     all p s = and (map p s)
     any p s = or (map p s)

 

Les fonctions partielles

(*2) 5 => 10
(2*) 5 => 10
(>0) 2 => True
(1/) 2 => 0.5
(/2) 8 => 4
(+1) 2 => 3


3.9) Quelques algorithmes courants

     
Signe de x signe x = if x>0 then 1
          else if x<0 then -1
          else 0
signe x
  |x>0 = 1
  |x<0 = -1
  |x==0 = 0
Factoriel de x f n = if n==0 then 1
              else
n*f(n-1)
f 0 = 1
f n = n*f(n-1)
Résolution de f(x)=0 par dichotomie :
On définie la fonction dicho qui prend en argument la fonction f les valeurs a et b de l'intervale de départ et l'erreur (la taille de l'intervale finale)

dicho f a b e =
  let c = (a+b)/2 in
    if abs(b-a)<e then c
    else if (f a)*(f c)<0 then dicho f a c e
    else dicho f c b e

dicho f a b e
    |abs(b-a)<e = c
    |(f a)*(f ((a+b)/2))<0 = dicho f a c e
    |otherwise = dicho f c b e
  where c=(a+b)/2
Les triplets pythagoriciens x^2 + y^2 = z^2 : tp k=[(x,y,z)|let n=[1..k],x<-n,y<-n,z<-n,x*x+y*y==z*z]

tp k =
  let n=[1..k]; k2=k*k in
  tp(k-1)++[(u,v,k)|
u<-n,v<-n,u*u+v*v==k2]

Retrait de la première occurrence de x dans une liste ret x [] = []
ret x (y:s) = if x==y then s
                      else y:(ret x s)
ret x [] = []
ret x (y:s)
  |x==y = s
  |otherwise = y:(ret x s)
Différence (retrait d'une seul occurence) diff s [] = s
diff s1 (x:s2) = if elem x s1 then diff (ret x s1) s2
                              else diff s1 s2
diff s [] = s
diff s1 (x:s2)
  |elem x s1 = diff (ret x s1) s2
  |otherwise = diff s1 s2
Tri par segmentation : O(n*ln(n))
On prend une valeur et on sépare la liste en deux, les valeurs inférieurs et les valeus supérieurs. Puis une foie ces deux listes triées on les concatenne.

triseg [] = []
triseg (p:s) = triseg [x|x<-s, x<=p]
               ++ [p]
               ++ triseg [x|x<-s, x>p]

Tri par insertion : O(n^2)
On trie la liste moins le premier élément, et on place l'élément à la bonne place dans la liste triée.
triins[] = []
triins(x:s) = ins x (triins s)
ins x [] = [x]
ins x (y:s) = if x<y then x:y:s
                     else y:(ins x s)

triins[] = []
triins(x:s) = ins x (triins s)
ins x [] = [x]
ins x (y:s)
  
|x<y = x:y:s
  |otherwise = y:(ins x s)

Tri par fusion : O(n*ln(n))
On sépare la liste en deux et on fusionneles deux listes triées.

trifus[]=[]
trifus[x]=[x]
trifus s = fusion (trifus g) (trifus d)
  where(g,d) = separ s [] []
fusion s [] = s
fusion [] s = s
fusion (x:s)(y:t) = if x<y then x:(fusion s (y:t))
                           else y:(fusion (x:s) t)
separ[] p i =(p,i)
separ(x:q) p i = separ q (x:i) p
trifus[]=[]
trifus[x]=[x]
trifus s = fusion (trifus g) (trifus d)
  where (g,d) = separ s [][]
fusion s [] = s
fusion [] s = s
fusion (x:s)(y:t)
  |x<y = x:(fusion s (y:t))
  |otherwise = y:(fusion (x:s) t)
separ[] p i =(p,i)
separ(x:q) p i = separ q (x:i) p
Les parties d'un ensemble :
Les partie de la liste ne contenant pas le premier élément e aux quelles on ajoute les parties de la liste restante aux quelles on ajoute l'élément e.
parties[]=[[]]
parties(a:s)=
  let p = parties s in
     p ++ (map (\x->a:x) p)
Les permutations d'une listes d'éléments distincts
On prend successivement un élémént x suivi de toutes les permutations possibles des autres.
perm[]=[[]]
perm s=[x:t|x<-s,t<-perm(ret x e)]
ret x [] = []
ret x (y:s) = if x==y then s
                      else y:(ret x s)
perm[]=[[]]
perm s=[x:t|x<-s,t<-perm(ret x e)]
ret x [] = []
ret x (y:s)
  |x==y = s
  |otherwise = y:(ret x s)

Les nombres premiers
crible de la liste [2,3,4,5,6...]. On calcule le crible d'une liste comme suit : On retire les multiples du premier nombre de la liste dans le reste de la liste, et on ajoute ce nombre au crible du reste de la liste.

nPremiers = crible([2..])
crible(x:s) = x:crible(retmult x s)
retmult n (x:s) = if mod x n ==0 then ss else x:ss
  where ss = retmult n s
nPremiers = crible([2..])
crible(x:s) = x:crible(retmult x s)
retmult n (x:s)
  |mod x n ==0 = ss
  |otherwise = x:ss
  where ss = retmul n s
Les nombres premiers
cc(s) retourne la liste s en enlevant tous les multiples du premier élément de la liste s.
nPremiers = map head (iterate cc([2..])
cc(n:s) = [x|x<-s, mod x n \=0]
Liste des diviseurs propres facteurs n = [x|x<-[1..n-1], mod n x == 0]
Liste des diviseurs propres plus petit ou égale à la racine carré. facts n =
  [x|x<-[1..floor(sqrt(fromIntegral(n)))], mod n x == 0]

Les nombres parfaits
Ce sont les nombres qui sont égaux à la somme de leurs diviseurs propres

nParfait = filter (\n->sum(facteurs n) == n) [1..]
Suite des factoriels sf = 1:zipWith (*) sf [1..]
Suite de Fibonacci sfb =1:1:[u+v|(u,v)<-zip sfb (tail sfb)]
Suite de Fibonacci sfb =1:1:zipWith (+) sfb (tail sfb)
Triangle de Pascal pascal = iterate(\s->zipWith(+)([0]++s)(s++[0])) [1]

L'évaluation en profondeur d'abord (innermost) signifie que pour chaque f(x) l'argument x doit être évalué avant la fonction f sur son argument x. Et lorsque toutes les opérations s'évaluent de gauche à droite, cela autorise alors la notation polonaise ou postfixées qui permet comme en Forth de commencer l'évaluation d'une expression au fur et à mesure de sa lecture. L'inconvénient de cette méthode est que l'évaluation de tous les arguments est toujours faites en premiers même si cela n'est pas utile pour le calcul final.

Par contre l'évaluation paresseuse, l'évaluation par l'exterieur d'abord (outermost) signifie que pour chaque f(x) on tente d'évaluer f sur son argument x d'abord, et on évalue x que si c'est necessaire.

f(x)=f(x+1)
g(x)=3
a=1/0

g(f 4) => 3
g a =>3
a => Infinity
f 2 => ne s'arrète jamais...

Exemple de fonction récurcive primitive f(n,0) = n+1
f(n,m+1) = f(f(n,m),m)

La fonction d'ackermann
Exemple de fonction récurcive non primitive

ak(x,y) = if x==0 then y+1 else ak (x-1,if y==0 then 1 else ak (x,y-1)) ak (x,y)
  |x==0 = y+1
  |y==0 = ak(x-1,1)
  |otherwise = ak(x-1,ak(x,y-1))
Le backtracking
Chercher par balayage en profondeur d'abord, le chemin qui arrive à un état satisfaisant p, à partir d'un état initiale e0, par le moyen d'une fonction s donnant pour chaque états la liste des états suivants possibles. La fonction ariane retourne le couple formé d'un booléen (True si solution trouvé, ou False), et de la liste inversée complètes des états permettant de passer de e0 au premier état satisfaisant p rencontré.
ariane s p e0 = f [e0][] where
  f [] ch = (False,ch)
  f(x:q) ch = if p x then (True,x:ch)
              else if elem x ch then f q ch
              else f ((s x)++ q)(x:ch)
ariane s p e0 = f [e0] [] where
  f [] ch = (False,ch)
  f(x:q) ch =
    |p x = (True,x:ch)
    |elem x ch = f q ch
    |otherwise f ((s x)++q) (x:ch)

Les types définie par constructeurs


 

Les commandes de plateforme :

 
:load fichier :l fichier Charge le fichier fichier.hs le prompt devient Fichier>
:load :l Enlève toutes les définitions chargées à l'exception du "Prelude"
:browse module

:bro module

Donne la liste des fonctions et leur type dans le module module
 
 
 
 
 
 

64 exercices...
:load nom.hs charge le fichier spécifié. En chargeant un fichier "essai.hs" le prompt devient essai>
:load enlève toutes les définitions chargées à l'exception du "prelude"
:also autre.hs charge un autre fichier
:reload répète la dernière commande de chargemant
:edit nom.hs Ouvre le fichier nom.hs
:edit edite le dernier module
:module <module> pose un module for evaluating expressions
:type <expr> affiche le type d'une expression
:? affiche cette liste de commandes
:set <options> set command line options
:set help on command line options
:names [pat] édite tous les noms contenus
:browse <modules> donne la liste des fonctions et leur type dans le module indiqué
:gc force le garbage collecteur
:quit exit Hugs interpreter

 

 

 

 

 

 

Une expression s'évalue en une valeur et possède un type static.

Il y a 6 sortes d'identificateurs :

Type de constructeur de nom différent que type classe. Les noms peuvent être qualifié sauf les typvar et les modid.

3.2 Variables, Constructors, Operators, and Literals
aexp -> qvar (variable)
| gcon (general constructor)
| literal

gcon -> ()
| []
| (,{,})
| qcon
var -> varid | ( varsym ) (variable)
qvar -> qvarid | ( qvarsym ) (qualified variable)
con -> conid | ( consym ) (constructor)
qcon -> qconid | ( gconsym ) (qualified constructor)
varop -> varsym | `varid ` (variable operator)
qvarop -> qvarsym | `qvarid ` (qualified variable operator)
conop -> consym | `conid ` (constructor operator)
qconop -> gconsym | `qconid ` (qualified constructor operator)
op -> varop | conop (operator)
qop -> qvarop | qconop (qualified operator)
gconsym -> : | qconsym

 

 

 

ghc -o prog --make Main

ghc -o executable --make ModuleMain

Commande
Re
Appel exemple
Description
:help
:?
:h
:?
:help
Affiche la liste des commandes
:cd
:c
:cd C:\dir1
Change le répertoire courant à C:\dir1
:load
:l
:load
:load
Toto
:load *Toto
Charge le module Prélude
Charge le module compilé contenu dans le fichier Toto.hs
Charge le module non-compilé contenu dans le fichier Toto.hs
:add
:a
:add Toto
:add *Toto
Charge le module compilé contenu dans le fichier Toto.o et conserve les références des autres modules.
:module
:m

:m
+*Toto
:m +Toto
:m -Toto
:m -*Toto
Spécifit le contexte pour l'évaluation des expressions :
Active le module non-compilé Toto
Active le module compilé Toto
Désactive le module compilé Toto
Désactive le module non-compilé Toto
:browse
:bro
:load Toto
 
:info
:i
:load Toto
 
:type
:t
:load Toto
 
:!
:!
:! dir
 
:main
:ma
:load Toto
 

 

Commande
Re
Appel exemple
Description
:!
:h
:?
:! ghc -o Toto Toto.hs
Affiche la liste des commandes
:cd
:c
:cd C:\dir1
Change le répertoire courant à C:\dir1
:load
:l
:load
:load
Toto
:load *Toto
Charge le module Prélude
Charge le module compilé contenu dans le fichier Toto.hs
Charge le module non-compilé contenu dans le fichier Toto.hs
:add
:a
:add Toto
:add *Toto
Charge le module compilé contenu dans le fichier Toto.o et conserve les références des autres modules.
:module
:m

:m
+*Toto
:m +Toto
:m -Toto
:m -*Toto
Spécifit le contexte pour l'évaluation des expressions :
Active le module non-compilé Toto
Active le module compilé Toto
Désactive le module compilé Toto
Désactive le module non-compilé Toto
:browse
:bro
:load Toto
 
:info
:i
:load Toto
 
:type
:t
:load Toto
 
:!
:!
:! dir
 
:main
:ma
:load Toto
 

Le chemin de recherche pour la découverte de fichiers source est spécifié avec l'option-i

 

La programmation en Haskell se fait dans des modules mis dans des fichiers d'extension hs ou lhs, un module par fichier. Par commodité on utilisera le même nom de module pour le fichier. Les module peuvent être emboités, et le module Data.Bool.Arbre correspond alors au fichier Data\Bool\Arbre.hs

 
:help       # List the diffent types of commands
:?

Spécifit le répertoire courant, ici le répertoire c:\dir1

:cd c:\dir1
:c c:\dir1

Spécifier le chemins de recherche des sources, ici les deux répertoires c:\dir1 et c:\dir2

:set -ic:\dir1:c:\dir2
:s -ic:\dir1:c:\dir2

Initialise le chemin à vide, (le répertoire courant)

:set -i
:s -i

:info map
:i map

:browse GHC.Base
:bro GHC.Base

:browse! GHC.Base

:module +Complexe
:m +Complex

:cd   dir   # Change the current directory
:load file  # Read and eval the contents of a file
:reload     # Read and eval the contents of the last file
  

:type expr
:t expr

Debugging

One tip about debugging. A handy function called trace is in the module Debug.Trace. It was the side effect of printing (to stderr) a string and can use used in the middle of computing values to print out an intermediate result.
Debug.Trace.trace :: String -> a -> a
  
It should be used for debugging only, i.e., not for normally printing of results.
f x = g (x+y) (x*y)
f x =
  trace ("f: x=" ++ (show x) ++ ", y= " ++ (show y) ++ "\n")
  (g (x+y) (x*y))

Single line comments are preceded by ``--'' and continue to the end of the line. For example:

7 - 4   -- this is subtraction
Multiline and nested comments begin with {- and end with -}. Thus
{- this is a
   multiline
   comment -}





The literate programming style in Haskell allows you to write *.lhs files in which every line is a comment by default. A line is Haskell code only when it begins with ">".

Anything is a comment unless it begins with ">".
> main = print [ (n, product[1..n]) | n <- [1..20]]
Files that don't have the lhs suffix can be compiled by the compiler by using the -x option.
shell> ghc -x lhs haskell.html
 

Dans l'interpreteur WinGHCi (ou GHCi) Spécifier les répertoires contenant les fichiers sources par exemple c:\dir1 et c:\dir2, à l'aide de la commande suivante :

:set -ic:\dir1:c:\dir2

ou alors déplacer le répertoire courant à l'aide de la commande suivante :

:cd c:\dir1

 

 

 

La programmation en Haskell se fait dans des modules mis dans des fichiers d'extension hs, un module par fichier. Par commodité on utilisera le même nom de module pour le fichier. Les module peuvent être emboités, et le module Data.Bool.Arbre correspond alors au fichier Data\Bool\Arbre.hs

On crée un répertoire de travail C:\Haskell. Et dans l'intérpréteur, on se positionne sur ce répertoire à l'aides de la commande :

:cd C:\Haskell

On peut exécuter toute commande MSDOS dans l'interpreteur GHCi (ou Hugs) à l'aide de la commande

:! <command>

Et obtenir une aide sur la liste des commandes avec

:?

On charge le fichier du module dans l'interpréteur GHCi (ou Hugs) à l'aide d'une des commandes :

:load C:\Haskell\toto.hs
:load C:\Haskell\toto
:load toto.hs
:load toto
:l C:\Haskell\toto.hs
:l Toto

Puis on pourra lancer l'éditeur avec la comande :

:edit

En ayant préalablement définie l'éditeur que l'on souhaite utiliser avec la commande :

:set editor C:\Program Files\SciTE\SciTE.exe

 

 

 

La fonction factoriel

let f(n) = if n==0 then 1 else n*f(n-1)
f(5)
f(10)


=> 120
=> 3628800

Dans WinHugs la commande let doit comprendre une deuxième partie constituée par la commande in et qui délimite la porté du let.

let f(n) = if n==0 then 1 else n*f(n-1) in (f(3),f(5))

=> (6,120)

Le programme principale s'appel main est peut être lancé avec la commande :main

La fonction factorielle peut se programmer en deux instructions plus lisibles dans un module Std. Les noms de module commence obligatoirement par une majuscule :

Toto.hs

module Std where

f(0) = 1
f(n) = n*f(n-1)

On peut charger le module comme suit :

:load Toto


f(5)
f(10)
:main

[1 of 1] Compiling Main ( Toto.hs, interpreted )
=> Ok, modules loaded: Main.

=> 120
=> 3628800
=> 2432902008176640000

On peut compiler le module comme suit, mais les fonctionalités non déclarée externe ne sont plus utilisable. On peut alors le rechargé sous forme intérprétée :

:! ghc -c toto.hs
:load toto

f(10)
:main
main
:show modules

:load *toto
f(5)
main
:show modules


=> Ok, modules loaded: Main.

<interactive>:1:0: Not in scope: `f'
=> 2432902008176640000
=> 2432902008176640000
=> Main (toto.hs, toto.o)

[1 of 1] Compiling Main ( toto.hs, interpreted )
=> 120
=> 2432902008176640000

=> Main (toto.hs, interpreted)

 

do {putStrLn "hello"; return "yes"}


x <- return 42
print x
x

let x = 12
x

let x = error "help!"
print x

=> hello
   "yes"


=> 42
=> 42


=> 12


=> *** Exception: help!

 

let f(x,y) = x*y
f(2,3)
f 2 3

let f x y = x*y
f 2 3
f(2,3)


=> 6
Erreur ! ...


=> 42
Erreur ! ...


let {f op 0 = 0; f op x = x `op` f op (x-1)}
f (+) 3


=> 6

Pour autoriser les passages à la ligne dans un bloc de code soumis à l'interpréteur GHCi, on le commence par :{ et on le finit par }:, ainsi la dernière fonction peut s'écrire :

:{
let {f op 0 = 0;
     f op x = x `op` f op (x-1)}
}:
f (+) 3





=> 6

 

:{
let {x=2;
     y=[4,56];
     a="Bonjour";
     w=('a',2);
     f x = x**x}
}:

:show bindings









=> a :: [Char] = _
   f :: (Floating a) => a -> a = _
   w :: (Char, Integer) = ('a',_)
   x :: Integer = _
   y :: [Integer] = [_,_]

Si vous activer l'option t, l'intérpréteur GHCi montrera le type de chaque variable affectée :

:set +t
let x=("Bonjour",4)
let f n = n^2
let f n = n**2

let (x:y) = [1,2,3]
x
y


=> x :: ([Char], Integer)
=> f :: (Num a) => a -> a
=> g :: (Floating a) => a -> a

=> 1
=> [2,3]

let { f op n [] = n ; f op n (h:t) = h `op` f op n t }
f (+) 0 [1..3]

L'inférence des types : L'ordinateur reconnait le type le plus générale de l'object lors de sa construction sans qu'il n'y est besoin que son type soit défini préalablement.

Le polymorphisme paramétrique : une fonction peut s'appliquer à des paramètres avec des types différents d'un appel à l'autre, et produire un résultat de type différents, dépendant des types des paramètres et de la fonction.

Toto.hs

module Md1 where
f(0) = 1
f(n) = n*f(n-1)


Titi.hs

module Md2 where
import Md1
g(x) = x**x/f(x)