Programmation Avancée
TP 12: un chat dans une monade
Dans ce TP en plusieurs parties, nous allons découvrir la bibliothèque de threads coopératifs Lwt, et finalement l'utiliser pour construire une petite application de messagerie instantanée pair-à-pair.
Pour commencer, il vous faut installer la librairie lwt
,
ainsi que la librairie lambda-term
que nous utiliserons pour
gérer les entrées-sorties dans le terminal.
Pour cela nous allons utiliser opam
, que vous commencerez
donc par initialiser si ce n'est déja fait:
opam init # Opam va vous proposer de modifier votre bashrc # pour qu'il s'auto-initialise dans chaque shell # nouvellement ouvert. Il est recommandé d'accepter. # Pour en profiter, ouvrez ensuite un nouveau shell # ou faites une initialisation manuelle de votre # environnement: eval `opam config env`
Ensuite on demande à opam
d'installer lwt
localement à
votre compte utilisateur:
opam install camlp4 opam install lwt opam install lambda-term
Récupérez alors le Makefile
qui vous permettra de faire make foo
pour construire un binaire
à partir d'un unique fichier
foo.ml
utilisant les modules et l'extension de syntaxe de Lwt.
Tester ce système en compilant et exécutant le fichier
helloworld.ml.
Partie 1: se familiariser avec lwt
Ci-dessous, un minimum d'information est donné à propos de lwt. En complément, on consultera le manuel officiel.
Les threads coopératifs lwt forment une monade, dont les opérations de
base sont Lwt.bind
et Lwt.return
. Dans le modèle lwt, un thread
s'exécute potentiellement dès qu'il est créé. Cependant, il y a quand
même une fonction Lwt_main.run
qui sert à activer le scheduler lwt
jusqu'à ce que le thread passé en argument se termine.
Sur ce noyau, on a des opérations
comme Lwt.join
pour exécuter deux threads en parallèle. La bibliothèque
fournit aussi des tâches primitives, notamment Lwt_io.printf
, version
non-bloquante / coopérative de printf
. (Toutes
ces fonctions sont documentées dans le manuel, et je vous invite à ouvrir
les pages correspondant à ces modules, par exemple celle pour
Lwt_io.)
Dans le fichier hello.ml
définir une fonction
repete : string -> int -> unit Lwt.t
telle que repete s n
affiche n
lignes numérotées
et contenant le message s
,
avec un délai aléatoire de moins d'une seconde entre chaque
ligne.
Exécuter en parallèle deux instances de repete
pour dire bonjour
dans deux langues de votre choix. Votre application ne devra pas
terminer avant que les deux threads aient effectué tous leurs
outputs.
Réécrire votre programme en utilisant l'extension syntaxique documentée
brièvement sur la première page du manuel lwt. On utilisera notamment
le notation lwt x = .. in ..
pour le bind, >>
pour la séquence,
et éventuellement un petit for_lwt
.
Partie 2: un jeu palpitant
Les threads lwt peuvent (en général) être "annulés" (interrompus / tués).
Cela peut être fait explicitement via la fonction Lwt.cancel
, mais aussi
implicitement par exemple avec la fonction pick
qui, comme join
,
exécute plusieurs tâches en parallèle, mais dès qu'une tâche termine annule
les autres.
Ecrire une fonction
timeout : float -> (unit -> 'a t) -> 'a t -> 'a t
tel que timeout t kill p
se comporte comme p
si celle-ci termine
en moins de t
secondes, et sinon éxécute la tâche kill
.
On utilisera Lwt.pick
pour cela.
Tester avec le jeu suivant où l'utilisateur doit répétitivement entrer une ligne avant un timeout d'une seconde:
let kill () = Lwt_io.printf "Game over!\n" >> Lwt.fail (Failure "over") let rec f () = Lwt_io.printf "Vite, une ligne!\n> " >> lwt l = timeout 1. kill (Lwt_io.read_line Lwt_io.stdin) in Lwt_io.printf "Merci pour ces %d caractères...\n\n" (String.length l) >> f () let () = Lwt_main.run (f ())
A suivre...
Par ici!