Contrairement à ce que certaines légendes laissent penser, Haskell n'est pas qu'un formalisme pour chercheurs en informatique théorique. C'est avant tout un langage de programmation utilisable et utilisé. Par exemple, saviez-vous que Facebook a développé et mis en production un détecteur de spam développé en Haskell ?

Dans ce tutoriel, nous allons voir comment implémenter une application simple (un serveur de blog basique) en Haskell. Il n'est pas nécessaire de connaître le langage au préalable. L'objectif est d'illustrer, à travers un exemple particulier, l'intérêt plus général de la programmation fonctionnelle à typage statique.

Ce tutoriel s'inspire d'un exemple du livre : La programmation fonctionnelle - Introduction et application en Haskell à l’usage de l’étudiant et du développeur. Les codes sources sont disponibles sur ce dépôt.

I-A. Pourquoi utiliser un langage comme Haskell▲

Haskell est un langage fonctionnel (basé sur la notion de fonction sans effet de bord) à typage statique (vérification des types pendant la compilation). Il existe d'autres langages de ce type, notamment OCaml, Scala et Rust. Ces caractéristiques sont de plus en plus reconnues dans les langages modernes. Par exemple, les fonctions lambdas des langages fonctionnels ont été intégrées dans la plupart des langages traditionnels : Java, C++, JavaScript, Python… De même, le typage statique est très répandu (Java, C++…) et de nombreux langages à typage dynamique ont des variantes à typage statique : TypeScript pour JavaScript, Mypy et les « type hints » pour Python… Les langages fonctionnels à typage statique ont plusieurs avantages : ce sont généralement des langages très expressifs, qui permettent un code concis ;

les langages fonctionnels utilisent principalement des fonctions pures, sans effet de bord, ce qui réduit les sources d'erreurs possibles ;

le typage statique permet de vérifier la cohérence du code via le compilateur, donc précocement et exhaustivement.

L'application à réaliser est un système de blog basique contenant deux pages : une page d'accueil (avec les messages de blog) et une page d'à-propos. Pour une vraie application, on ajouterait certainement une page d'édition avec un système d'authentification, mais on veut ici rester simple. L'architecture choisie est un serveur web de pages dynamiques, c'est-à-dire un programme qui reçoit des requêtes HTTP et génère, en réponse, des pages HTML. Pour générer la page d'accueil, on lit les messages de blog dans une base de données initialisée avec le code SQL suivant (fichier simpleblog.sql) : Sélectionnez 1.

2.

3.

4.

5.

6.

7.

8.

9.

10.

11.

12.

13.

14.

15.

16.

17.

18.

19.

20.

21.

22.

23.

24.

CREATE TABLE messages ( id INTEGER PRIMARY KEY , author TEXT , title TEXT , body TEXT ) ; INSERT INTO messages VALUES ( 0 , "Wikipedia" , "Programmation fonctionnelle" , "La programmation fonctionnelle est un paradigme de programmation de type déclaratif qui considère le calcul en tant qu'évaluation de fonctions mathématiques." ) ; INSERT INTO messages VALUES ( 1 , "Wikipedia" , "Haskell" , "Haskell est un langage de programmation fonctionnel. Il est fondé sur le lambda-calcul et la logique combinatoire. Son nom vient du mathématicien et logicien Haskell Brooks Curry." ) ; Cette base est donc composée d'une table messages remplie avec deux lignes de données. On utilise le SGBD SQLite, qui s'intègre directement dans l'application et stocke les données de la base dans un fichier. Pour générer le fichier de base simpleblog.db, à partir du fichier SQL précédent, on lance la commande : sqlite3 simpleblog.db < simpleblog.sql

I-C. Début de l'implémentation▲

On peut désormais écrire le code Haskell de l'application. Tout d'abord, on indique les extensions de langage à utiliser et les bibliothèques à importer. Sélectionnez 1.

2.

3.

4.

5.

6.

7.

8.

9.

import Control. Monad (forM_) import Control. Monad .Trans (liftIO) import qualified Data.Text.Lazy as L import Database.SQLite.Simple (query_, withConnection) import Database.SQLite.Simple.FromRow (FromRow, fromRow, field) import Lucid import Web.Scotty (get, scotty, html) Sans entrer dans le détail, on utilise les bibliothèques sqlite-simple pour accéder à la base de données, lucid pour générer les pages HTML et scotty pour implémenter le serveur web.

I-D. Implémentation du modèle▲

La partie modèle de l'application est essentiellement composée des messages de blog. Pour les représenter, on définit le type de données suivant : Sélectionnez 1.

2.

3.

4.

5.

data Message = Message { _author :: L.Text , _title :: L.Text , _body :: L.Text } Ainsi, le type Message représente des valeurs Message contenant trois champs de texte, nommés _author, _title et _body. Définir des types pour les données que l'on manipule permet notamment de rendre le code plus lisible et de vérifier sa cohérence, via le compilateur. Pour récupérer un message à partir d'une ligne de la base de données, on indique que le type Message instancie la classe de types FromRow : Sélectionnez 1.

2.

instance FromRow Message where fromRow = Message <$> field <*> field <*> field Le mécanisme de classe de types (à ne pas confondre avec les classes de la programmation orientée objet) est un outil très puissant. Ici, il nous permet de définir la fonction fromRow pour le type Message et de construire une valeur Message à partir de trois champs d'une ligne de données. On peut alors récupérer tous les messages du fichier de base de données simpleblog.db avec la fonction selectMessages suivante : Sélectionnez 1.

2.

3.

selectMessages :: IO [Message] selectMessages = withConnection "simpleblog.db" req where req c = query_ c "SELECT author,title,body FROM messages" Cette fonction sélectionne les trois champs author, title et body de la table messages. Chaque ligne résultat est automatiquement traitée par la fonction fromRow, car la fonction query_ est définie pour tout type de classe FromRow, dont ici Message. Finalement, on obtient une liste de messages, c'est-à-dire une valeur de type [Message]. La ligne selectMessages :: IO [Message] est la signature (optionnelle) de la fonction. Elle indique que selectMessages ne prend aucun paramètre et retourne une liste de messages. IO indique que la fonction peut réaliser des entrées-sorties (les accès à la base de données). La fonction est donc non-pure et ne peut être appelée que dans des fonctions autorisant les entrées-sorties. Cette fonctionnalité est très intéressante, car elle impose de définir quelles fonctions peuvent réaliser des effets de bord. Lors de la compilation, la gestion des effets de bord est alors vérifiée sur l'ensemble du code, ce qui évite de nombreuses erreurs d'exécution potentielles.

I-E. Implémentation des vues▲

Pour générer des pages HTML, la bibliothèque lucid redéfinit les balises HTML sous forme de fonctions Haskell. Par exemple, pour générer le code HTML <h1>À propos</h1>, on écrit le code Haskell h1_ "À propos". Ceci permet de simplifier le code à écrire et surtout profiter du système de typage de Haskell. Par exemple, si on demande une balise HTML qui n'existe pas, le compilateur indiquera une erreur. Ainsi, la page d'à-propos de notre application peut être générée avec le code Haskell suivant : Sélectionnez 1.

2.

3.

4.

5.

6.

7.

aboutView :: L.Text aboutView = renderText $ do doctype_ html_ $ body_ $ do h1_ "À propos" p_ "Ceci est un blog sur Haskell, codé en Haskell." p_ $ a_ [href_ "/" ] "Accueil..." Ici, la notation do permet de définir plusieurs éléments HTML à l'intérieur d'un même élément parent. Par exemple, l'élément body est composé d'un élément h1 et de deux éléments p. Le second élément p contient un unique élément (a), d'où l'absence de do. Quant à la page d'accueil, elle dépend des messages de blog à afficher. On définit donc une fonction homeView qui prend en paramètre une liste de Message et retourne le texte du code HTML correspondant : Sélectionnez 1.

2.

3.

4.

5.

6.

7.

8.

9.

10.

homeView :: [Message] -> L.Text homeView messages = renderText $ do doctype_ html_ $ body_ $ do h1_ "Mon blog sur Haskell" forM_ messages $ \ m -> p_ $ div_ $ do strong_ $ toHtml $ _title m toHtml $ L.concat [ " par " , _author m ] div_ $ toHtml $ _body m p_ $ a_ [href_ "/about" ] "À propos..." L'expression forM_ applique une fonction lambda à chaque Message de la liste messages. Cette lambda prend un message m et génère son formatage HTML dans la page (un div contenant le titre en gras suivi de l'auteur, puis le corps du message dans un div imbriqué).

I-F. Implémentation du programme serveur▲

Enfin, pour implémenter le programme principal, la bibliothèque scotty permet de créer un serveur HTTP avec un système de routage d'URL : Sélectionnez 1.

2.

3.

4.

main :: IO () main = scotty 3000 $ do get "/about" $ html aboutView get "/" $ liftIO selectMessages >>= html . homeView La fonction scotty demande de lancer un serveur HTTP qui écoute sur le port 3000 et qui gère les routes définies ensuite. Pour la route "/about" (via la méthode HTTP GET), on renvoie directement la vue aboutView (c'est-à-dire du code HTML). Pour la route "/", on récupère les messages de la base de données avec la fonction selectMessages et on les transmet à la fonction homeView pour générer la page HTML à renvoyer. Enfin, on notera la signature d'une fonction main en Haskell : main :: IO(). Il s'agit donc d'une fonction qui ne prend aucun paramètre et ne retourne aucune valeur mais qui peut réaliser des entrées-sorties, comme appeler la fonction selectMessages.