Haskell Valid XHTML 1.1Cette page est conforme à la norme CSS!Mise à jour 06-2010
SOMMAIRE

Présentation
    Principe
    Structures de données
    Autres points
Les exemples
Les exemples avec wxHaskell
Les outils et liens
Télécharger les exemples
PRESENTATIONRetour en haut de page

Les principes

Il existe plusieurs paradigmes de programmation. Les plus connus sont :
  • la programmation impérative
    Le classique.
    Les instructions, exécutées dans l'ordre adéquat, modifient les valeurs des variables pour obtenir le résultat souhaité.
  • la programmation objet
    Bien connue maintenant.
    Les traitements et les données sont encapsulés ensemble, ce qui augmente la sécurité vis à vis des effets de bord, toujours indésirables.
  • la programmation par contrat
    Les traitements possèdent des conditions d'entrée et des conditions de sortie qui doivent être respectées.
  • d'autres encore
Haskell lui, obéit au paradigme fonctionnel.
Basé sur des concepts mathématiques, il présente un aspect plus déclaratif, c'est-à-dire que l'ordre des instructions est indifférent. Par exemple, quand on écrit classiquement : x=1; y=2*x, en haskell on peut l'écrire ainsi ou à l'envers : y=2*x; x=1.

Fonctionnel signifie que les traitements sont réalisés par l'intermédiaire de fonctions, qui ne dépendent que des paramètres qu'elles reçoivent, et non de variables extérieures. Un programme est donc un enchainement de fonctions.
La transparence référentielle stipule qu'une fonction, pour des paramètres donnés, doit toujours donner le même résultat.
Une conséquence surprenante : les variables ne sont pas modifiables, donc pas d'effet de bord. Alors, comment incrémente-t-on une variable ?
On écrit f x = x + 1 et on passe cette fonction à la fonction désirée.
Et une boucle ? On écrit une fonction récursive, c'est-à-dire qui s'appelle elle-même.
L'exemple classique est la factorielle n! = n * (n-1) * (n-2) ... * 1 que l'on peut exprimer aussi n! = n * (n-1)!

Exception notable à la transparence référentielle : les entrées-sorties. En effet, une lecture-clavier , sans paramètre d'entrée, donne un résultat toujours différent (la saisie effectuée).
Il existe des mécanismes pour tenir compte de cette particularité et ne pas "casser" l'aspect fonctionnel du programme.

Les listes constituent une structure de données essentielle en Haskell. Ici encore, les fonctions récursives permettent de balayer les listes et de réaliser des traitements sur celles-ci.

Enfin, Haskell est un langage à évaluation "paresseuse" : les fonctions sont évaluées lorsque nécessaire.
Par exemple si on a : a=1; b=2; c=b+1, l'évaluation de a n'est pas nécessaire dans le calcul de c, donc ne sera pas exécutée.
Ce mécanisme autorise Haskell à définir des listes infinies, qui devront être limitées au moment de leur évaluation. Ainsi, la liste des entiers impairs s'écrit [1, 3, ..], le .. signifiant à Haskell de "continuer la liste".

Les données et structure de données

Les données et fonctions sont typées; soit des types prédéfinis (entiers, flottants, booléens, caractères, ...), soit des types créés par le programme.
Il existe des types récursifs, par exemple un arbre est constitué de feuilles et de sous-arbres, eux-mêmes etc...

La compilation du source contrôle que les appels de fonctions respectent les types définis. Haskell facilite cependant la tâche en pratiquant l'inférence de type, c'est à dire en "devinant" le type selon le contexte. Quoiqu'il en soit, préciser le type permet de clarifier le source.

Au niveau des structures de données, on trouve :
  • les listes
    Une liste est un ensemble (noté [a,b,...,n], de longueur quelconque, de valeurs possédant le même type. On ne peut mélanger des entiers, des booléens etc...
  • les tuples (n-uplets)
    Un tuple est un ensemble d'éléments (noté (a,b,...,n) ), en nombre fini, qui peuvent contenir des données de types différents. On le rapproche des formats d'enregistrements ou records d'autres langages.
Une liste peut bien sûr être composée de tuples ou de listes, et réciproquement, tout ceci avec plusieurs niveaux d'imbrication.

Autres points à étudier

Haskell est implémenté de manière modulaire, avec un Prélude, que l'on peut compléter (import) de multiples modules et packages, qui apportent de nouveaux types et fonctions.

Voir la documentation en ligne via hoogle (référence en fin de page) pour des détails sur ceux-ci.

A voir également deux points importants : les monades et les arrows.

Enfin, une petite partie est consacrée à un environnement graphique, wxHaskell.


LES EXEMPLES DETAILLESRetour en haut de page

Les quelques exemples ci-dessous sont l'occasion de tester certaines fonctionnalités du langage.
Ils ont suivi des premiers pas hésitants, qui ont servi à découvrir les fondements du langage, et sont à ce titre révélateurs des questions qui viennent à tout débutant, lorsqu'il commence à créer de "vrais" programmes.

Jeu de pendu (hangman1.hs)

Le programme réalise un jeu de pendu.
On y trouve la logique classique d'un programme :
  • saisie de donnée et contrôle de celle-ci,
  • traitement de la saisie,
  • application des règles du jeu,
  • affichage de résultats intermédiaires,
  • re-bouclage du programme
Cette logique n'a rien d'extraordinaire mais introduit dans haskell le concept des IO qui sortent du strict cadre fonctionnel et sont souvent, pour les débutants, un casse-tête.
Et aussi des manipulations de listes multiples : les lettres restantes, le mot à trouver, la solution en cours de constitution.
On peut appeler le programme en lui passant le mot à trouver (secret xxxxxxxxxx) ou en lui laissant réaliser cette fonction de saisie (lancer par main).

Ce programme est une de mes premières réalisations et les commentaires apportés par des participants de la mailing list ne sont pas la partie la moins intéressante.
Certaines fonctions conçues de façon naïve peuvent ainsi être écrites plus intelligemment.

Nombres en lettres (nombres_lettres.hs)

Le programme a pour objectif de transformer sous forme littérale un nombre donné. Il gère plusieurs langues.

Les différentes parties du source utilisent diverses ressources courantes, listées ci-dessous.

Chargement des paramètres
  • Lecture d'un fichier en une seule fois,
  • Eclatement en lignes,
  • Partage récursif de chaque ligne en champs, séparés par des ";",
  • Filtrage de listes,
  • Retour sous forme d'un tuple de deux listes.
Traitement de conversion
  • Définition de synonymes de types,
  • Fonctions récursives,
  • Utilisation de gardes dans les définitions de fonctions,
  • Utilisation de gardes dans les instructions case,
  • Fonctions imbriquées,
  • Traitement de listes (mapM_, head, !!)

Trace (trace2.hs)

L'intérêt du programme réside dans l'implémentation d'une fonction de trace qui s'insère et s'enlève très facilement et est plus simple que l'emploi du debug interactif.
En effet, l'ajout d'une trace par un simple putStrLn change le type de la fonction testée, qui devient IO (xxx) et doit être réécrite sous forme de do.
Cette modification impacte le reste du programme, ce qui rend complexe le débogage du programme.

Pour illustrer ceci, un exemple simple de traitement itératif permet de mettre en oeuvre cette trace.

Le programme doit compléter une liste de valeurs, dont certaines sont à zéro.
Pour ceci, il doit essayer successivement les valeurs issues d'une liste des disponibles, en respectant l'unicité des valeurs dans la liste finale.

Utilisation de debug
L'instruction debug est utilisée dans sa forme infixe caractérisée par
  • l'écriture du nom entre guillemets inversés (alt-gr-7),
  • l'ordre des paramètres : a f b au lieu de f a b
L'avantage est qu'elle s'ajoute en fin de ligne et se trouve donc très facile à ajouter et à enlever.

Traitement de fichiers (fileio.hs)

Le programme teste les fonctions d'accès aux fichiers :
  • écriture d'un fichier,
  • ajout dans un fichier,
  • modification d'un fichier (en mode binaire),
  • lecture d'un fichier en une fois,
  • lecture d'un fichier ligne par ligne,
  • récupération de données concernant le fichier.

Jeu de chiffres (chiffres.hs)

Le programme résoud les puzzles numériques proposés par le jeu TV "Des chiffres et des lettres".
A partir de 6 nombres, dont certains peuvent être égaux, il s'agit de reconstituer une valeur donnée, en utilisant au plus une fois chaque nombre, et en les combinant avec les quatre opérations de base.
Chaque combinaison correcte est enregistrée dans un fichier texte.

Le principe de calcul est celui-ci :
  • établir toutes les combinaisons de couples de nombres associés aux opérations,
  • calculer chaque combinaison; si le résultat final est trouvé, enregistrer la combinaison
    sinon le résultat intermédiaire ainsi calculé remplace dans la liste des nombres ceux ayant servi à son calcul et l'on détermine les nouvelles combinaisons possibles avec ce nouveau jeu de nombres.
Les combinaisons sont établies par rapport aux positions des nombres et non à leurs valeurs; ainsi un meme nombre à deux positions différentes pourra être utilisé deux fois.
Ce programme met en jeu :
  • les comprehensive lists pour calculer les combinaisons de nombres et d'opérations,
  • les filtres sur les listes de combinaisons pour éliminer des cas inutiles,
  • les manipulations de liste pour extraire des valeurs et ajouter des résultats intermédiaires,
  • la récursivité pour explorer les différentes imbrications de calculs et conserver la trace de celles-ci.

Jeu des cases (cases.hs)

Le jeu consiste, sur un tableau carré de 9 cases, à reconstituer une configuration de couleurs déterminée, les cases présentant au départ deux états, allumé ou éteint.
En choisssant une case, sa couleur et celle des cases voisine change; mais toutes les cases n'ont pas le même comportement vis à vis de leurs voisines.

Ce programme met en jeu :
  • la saisie contrôlée de valeur,
  • des traitements par map pour réaliser la boucle d'inversion des cases,
  • des traitements par mapM_ pour réaliser les boucles d'affichage des cases.

Sudoku (sudoku01.hs)

Il s'agit d'un solveur de Sudoku; il applique une méthode de recherche systématique des solutions en essayant pour chaque case les valeurs de 1 à 9.
Lorsqu'une valeur est acceptable, les cases suivantes sont examinées de la même manière. Lors d'un blocage, le programme retourne à la case précédente pour essayer une autre valeur.

La fonction de calcul est récursive, c'est ce qui permet la navigation d'avant en arrière dans la recherche des solutions. La difficulté dans ce genre d'algorithme est de trouver la fonction qui constitue le coeur du problème.

Un autre point intéressant est l'utilisation - sacrilège dans Haskell - d'un tableau modifiable qui simplifie considérablement le traitement en évitant des calculs fastidieux de nouveaux tableaux et leur passage comme paramètres.

Ce programme n'est pas de ma conception, mais a été publié sur le site haskelwiki dont les références sont plus loin.
Sa concision est assez étonnante et sa vitesse d'exécution satisfaisante, même sur une machine ancienne.

Mots-croisés (mots.hs)

Il s'agit du problème n° 99 des Ninety-Nine_Haskell_Problems.

La solution est ici élaborée en deux phases :
  • déterminer un lot de solutions acceptables
    Pour ceci on calcule toutes les combinaisons possibles des mots, puis on ne conserve que celles où la longueur des mots correspond à celle des cases devant les recevoir. Cette phase est assez lourde puisque le nombre de permutations est de factorielle N (nombre de mots).
  • filtrer les mots concordants
    Ne valider que les solutions où deux mots se croisant ont bien identité de leur lettre commune.
Ainsi, pour une liste de 9 mots, dont 2 de 6 lettres, 2 de 5, 1 de 4, 4 de 3, il existe 2! * 2! * 1! * 4! soit 96 possibilités; partant des permutations, il faut filtrer ces 96 parmi 9! (362880) - on passe à 39 916 800 pour 11 mots.
Une solution plus efficace consisterait à ne générer directement que les 96 cas.

Les opérations de génération des permutations, de filtrage sur longueur et sur lettres communes sont réalisées par un code très simple. La plus grande partie du code est dédiée à la transformation du tableau des cases en blocs (ligne, colonne, longueur et orientation) et en cases communes aux blocs 2 à 2, ainsi qu'à l'affichage de la solution sous forme de grille.

LES EXEMPLES GRAPHIQUES AVEC WXHASKELLRetour en haut de page

Les exemples qui suivent mettent en oeuvre l'environnement graphique wxHaskell, basé sur la bibliothèque wxWidgets.

Il n'obéit pas au paradigme fonctionnel (les valeurs et attributs des widgets sont modifiables) mais il permet cependant la création de formulaires avec les contrôles habituels (boutons, listes déroulantes, etc ...)

Les fonctionnalités de ce GUI font l'objet de deux modules à incorporer dans les programmes, dont le deuxième s'appuie sur le premier pour donner des fonctions un peu plus "sympathiques" :
  • Graphics.UI.WXCore,
  • Graphics.UI.WX

Tracé d'un graphe (graphe.hs)

Ce programme montre, avec une application simple, l'utilisation de différents types de widgets :
  • frame pour définir la fenêtre,
  • boutons de commande, boutons radios, zones de texte, slider, liste déroulante,
  • boîte de dialogue,
  • panel pour dessiner un graphique.
Il existe plusieurs modes de positionnement des contrôles à l'écran.
  • par position : indiquer les coordonnées x/y de chaque contrôle,
  • par layout, qui définit la manière dont les widgets s'agencent les uns par rapport aux autres.
Cete dernière méthode est plus difficile à maîtriser er rappelle un peu le système utilisé en Java (et son GridBagLayout).

Jeu des cases (caseswx.hs)

Ce programme est une reprise du programme cases.hs qui est, lui, en mode console.

La principale particularité est qu'il gère les boutons représentant les cases dynamiquement, c'est à dire qu'ils ne sont pas définis un à un par leur nom, mais dans une boucle qui retourne un tableau de références sur ces boutons. Les boutons sont anonymes.

On peut ainsi imaginer des applications où le nombre et la disposition des widgets ne sont pas pré-déterminés mais dépendent du contexte.

WxHaskell et base de données

Cette application permet de gérer une bibliothèque avec une interface graphique, en s'appuyant sur une base de données.
Elle nécessite pour ceci d'utiliser un menu, plusieurs frames, une base de données, des listes déroulantes à partir de la bd, ceci à travers plusieurs fonctions :
  • Création/modification de livres,
  • Critères de recherche et de tri,
  • Exportation de fichier texte
Architecture
Un écran principal présente la liste des livres.
Des boutons et des menus permettent d'accéder à des fonctions supplémentaires (création, recherche, tri, exportation).

D'un point de vue technique, plusieurs choix ont été effectués :
  • la base est constituée sous access97, et accédée par un lien ODBC (des tests ont aussi été réalisés avec sqlite),
  • Les fonctions sont implémentées sous forme de modules distincts, ce qui permet(trait) d'appliquer des modèles lors de la conception des modules, tout en allégeant le source principal,
  • Les fonctions appelées à partir de l'écran principal présentent des fenêtres modales,
  • Les widgets dans les frames sont disposés par position et non par un layout,
  • Les variables que l'on doit conserver au travers des différents frames et des dialogues sont sous forme de variables mutables (varCreate). En effet, une interface graphique n'a pas l'aspect fonctionnel pur requis en Haskell (les widgets sont aussi des variables mutables),
  • Enfin, les fonctions sont capitalisées au maximum dans des fonctions générales.
Ecran principal (biblio.hs)
Cet écran possède deux fonctions principales :
  • Afficher la liste des livres,
  • Déclencher les fonctions annexes (menu, boutons, barre d'outils)
Les livres sont affichés dans un listCtrl.
La requête sql est modifiée par la fonction de recherche, qui choisit les critères de sélection (optionnels), et par la fonction de tri, qui choisit l'ordre de classement des livres sélectionnés. Elle est stockée sous forme de variable mutable.
Dès qu'une modification des critères est validée, la liste des livres est actualisée, ainsi qu'après une création ou modification de livre.

La requête est exécutée par la fonction dbQuery qui, pour chaque ligne lue, exécute une fonction chargée d'extraire les données par des instructions dbRowGetxxxxx, données retournées sous forme d'un tuple, et accumulées dans une liste.

L'écran est complété de :
  • Barre de menu : menuBar (barre), menuPane (menu Fichier par ex.), menuItem (options),
  • Barre d'outils : toolBarEx avec des boutons graphiques, pour déclencher soit des options de menu toolMenu, soit des options hors menu toolItem,
  • Barre de statut : statusBar, statusField, pour afficher des compteurs (sélection de livres, exportation)
Fonction de recherche (module BiblioRech.hs)
L'écran est un frame affiché sous forme de boîte de dialogue modale. Ceci nécessite de référencer l'écran principal et de créer un frame fictif pour éviter des problèmes d'écrasement.
Les critères possibles, dont les valeurs sont optionnelles, sont :
  • Titre du livre (texte) : textEntry,
  • Auteur (nom court) en liste déroulante : choice, chargée à partir d'une table de la base,
  • Type de livre : idem ci-dessus
Trois fonctions sont nécessaires pour traiter les listes déroulantes :
  • Chargement des valeurs de la liste à partir de la base,
  • Initialisation de la valeur de la sélection (reprendre la sélection précédemment effectuée),
  • Récupération de la valeur sélectionnée
En effet, la valeur affichée dans les listes est une valeur texte, laquelle est identifiée dans la base par une clé numérique incrémentée automatiquement.
La valeur de sélection est un n° de rang dans la liste. Il faut donc gérer parallèlement à la liste une liste des clés des arguments de la liste, de manière à trouver à partir d'une clé son n° d'ordre, et à partir d'un rang, la clé correspondante.

Ces fonctions sont inscrites dans le module BiblioGeneral.hs.

L'affichage modal s'effectue par la méthode showModal, qui a pour parcularité de passer en paramètre (souvent appelé stop) une fonction qui sera utilisée pour retourner la valeur finale issue du traitement de la boîte de dialogue.
Cette valeur est de type Maybe; en cas d'abandon du dialogue, Nothing est retourné.
En conséquence, le bouton OK constitue une valeur Just contenant les critères de recherche, tandis que le bouton Annuler retourne Nothing.

Fonction de tri (module BiblioTri.hs)
Le fonctionnement est analogue à celui de l'option de recherche.
Le choix du type de tri s'effectue par des boutons radio groupés par un radioBox.

Fonction d'export (module BiblioExport.hs)
La requête actuelle extrait les livres et les copie dans un fichier texte; les champs sont séparés par des tabulations et l'extension est xls, ce qui permet une ouverture directe par excel.
Une boite de dialogue fileSaveDialog permet de choisir le fichier de destination.

Fonction de création/modif de livre (module BiblioCreation.hs)
La création est accessible par une option de menu. La modification l'est par un double-clic sur une ligne de la liste des livres. La différence réside dans le paramètre n° de livre reçu: 0 en création, n° du livre choisi sinon.

La présentation du frame est très semblable à l'écran de recherche, à la différence près que les listes déroulantes pour les auteurs et les types ne proposent pas de valeur vide (pas de critère optionnel).
Là encore, les fonctions générales sur les listes déroulantes sont mises en application.

Les données sont vérifiées avant d'être inscrites dans la base; un message infoDialog est affiché en cas d'anomalie et le curseur revient dans le champ erroné.

Fonctions d'usage général (module BiblioGeneral.hs)
Ce module contient des procédures utilisées dans plusieurs programmes ou modules. On y trouve :
  • Chargement d'une liste à partir d'une table,
  • Initialisation de la valeur d'une liste,
  • Récupération de la valeur choisie dans une liste.
Autres fichiers
Le programme a été réalisé avec une base access 97.
Cependant, des tests ont été réalisés avec sqlite. Pour ceci, après avoir créé une base dans sqlite et le lien odbc correspondant :
  • lancer sous sqlite le script schema.sql pour créer les structures de tables,
  • lancer le script peuple.sql pour peupler les tables,
  • modifier le source de biblio.hs pour changer la connexion (nom du lien odbc).

LES OUTILS ET LIENSRetour en haut de page

Interpréteurs

GHCi 6.10.4, utilisé ici sous windows xp.
HUGS

L'interpréteur GHCi comporte le compilateur ghc, qui permet de créer des .exe
Disponible gratuitement en téléchargement.

Liens

Ces sites diffusent des cours de différents niveaux sur le langage.

developpez.com
wikibooks
site du zéro, initiation
Haskellwiki
Hoogle référence des modules et types de Haskell

Exercices

Sudokus en haskell
99 problèmes

Mailing list, internationale et active.

Création de fichier exécutable (windows)

Les programmes peuvent être compilés sous forme de fichier exe.
Le source doit pour ceci comporter une fonction main.

Un fichier compil.bat lance la création de l'exécutable. Celle-ci est déclenchée en tirant le module principal sur le raccourci du bat.
set chem=%cd%
set shs=%1
ghc --make %shs% -i%chem% -o%shs:hs=exe%
del %chem%\*.hi
del %chem%\*.o
pause
L'astuce du raccourci est nécessaire pour que %cd% récupère le chemin du module principal, où sont également stockés les modules annexes.
Ce chemin figure dans le répertoire de travail du raccourci.
Dans le path, il faut avoir le répertoire haskell\bin.