Programmation fonctionnelle / Développement web

Apprendre comment implémenter un serveur de blog avec le langage Haskell

Pour réagir au contenu de ce tutoriel, un espace de dialogue vous est proposé sur le forum. Commentez Donner une note  l'article (5)

Article lu   fois.

L'auteur

Profil Pro

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Programmation fonctionnelle et développement web, exemple d'un serveur de blog en Haskell

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.

I-B. Exemple d'application

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.

Image non disponible

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.
{-# LANGUAGE OverloadedStrings #-}

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.

I-G. Conclusion

Dans ce tutoriel, nous avons vu comment implémenter un serveur de blog basique en Haskell, de la réception des requêtes HTTP à l'envoi des pages HTML, en passant par les requêtes à la base de données. Le code complet est très concis, 37 lignes de code (à titre de comparaison, une implémentation en JavaScript est également fournie dans le dépôt de code). En plus d'une grande expressivité, Haskell propose un système de type évolué et une gestion explicite des effets de bord.

Le système de type permet de représenter des données et de composer efficacement des fonctions de traitement, par exemple pour lire une base de données ou pour générer du code HTML. La bibliothèque servant permet même de typer des URL, d'en composer des API et d'en dériver des clients et des serveurs ! De plus, le typage statique permet de vérifier la cohérence du code à la compilation et ainsi de détecter de nombreuses erreurs potentielles, précocement et exhaustivement.

La gestion explicite des effets de bord apporte encore davantage de sécurité. En effet, elle permet de distinguer le code pouvant réaliser des effets de bord (et donc source d'erreurs) du code pur (et donc « sûr »). Les effets de bord sont même représentés dans le système de type (par exemple, IO pour les entrées-sorties) et ainsi vérifiés par le compilateur.

Pour conclure, on notera que certains principes de la programmation fonctionnelle à typage statique sont applicables dans de nombreux langages ou influencent leur évolution. Par exemple en JavaScript, les bibliothèques Ramda et Lodash permettent de programmer dans un « style fonctionnel », Immutable.js apporte des structures de données « pures », Redux implémente une gestion d'état inspirée du langage fonctionnel Elm, TypeScript est un surensemble de JavaScript proposant un système de typage statique…

II. Note de la rédaction de Developpez.com

Nous tenons à remercier Image non disponiblef-leb pour la relecture orthographique de ce tutoriel.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Copyright © 2019 Julien Dehos. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.