Affichage des articles dont le libellé est ocaml. Afficher tous les articles
Affichage des articles dont le libellé est ocaml. Afficher tous les articles

mercredi 9 octobre 2024

Un moteur de jeu écrit en Ocaml

C'est ce type qui s'est lancé dans ce projet un peu fou : créer un moteur de jeu en OCaml. Étant fan d'OCaml et de moteurs de jeu, forcément, ça me parle. Et en plus le bougre décide d'implémenter un système de composant d'entitées !

Alors, malheureusement, c'est du stream, c'est donc pas très dense et je n'ai pas très envie de me taper 10 heures de vidéo pour saisir toutes les subtilités de son approche. Mais le tout début déjà me donne un bel aperçu ce que ce c'est de travailler avec du OCaml moderne, en utilisant au maximum OPAM et Dune. Je compte bien m'y mettre en dilettante un de ces quatre et voir ce que ça donne. Ça pourrait être également l'occasion de creuser un peu les nouveautés du langage autour de la programmation concurrente via les domaines.

La première étape, ça serait de porter mon control, parce qu'avec tous ces controlleurs de jeux sur et sous mon bureau, je n'ai qu'une envie, c'est de coder avec !

samedi 30 août 2014

Grosse mise à jour Debian

Ça occupe! Tout d'abord, Debian est passé à Postgresql 9.4. Depuis 9.0, Postgresql fait tout ce que je veux, je n'attendais pas particulièrement cette dernière version, mais ça fait toujours plaisir d'être à jour. La mise à jour est toujours aussi triviale, et mes vieilles notes toujours d'actualité. Il faut juste ne pas se tromper de cluster.

Ensuite, quelques changements mineurs dans Médoc, avec Eliom qui passe en 4.0, et change quelques noms de module, et une en-tête manquante dans le client C++. Le commit est donc trivial.

Étrangement, sur ma machine virtuelle au boulot, la mise à jour a voulu passer à systemd, mais pas à la maison. Le passage à systemd semble s'être bien passé, mais un souci avec les "Guest Additions" de VirtualBox m'empêche d'en profiter. Je continuerai donc à utiliser notre bon vieux sysV pour un moment.

mercredi 12 février 2014

Médoc de retour parmis les vivants!

Enfin! Il a fallu batailler pour comprendre les changements apportés à Eliom (la documentation mériterait quelques exemples supplémentaires), mais cette mise à jour est vraiment utile et rend le framework un petit peu plus facile à utiliser, une fois qu'on a compris le principe. J'ai pris quelques raccourcis pour réparer mon code, et il n'est donc pas d'une propreté à toute épreuve, mais l'ensemble du site fonctionne. Étrangement, les CSS ont eu l'air de souffrir un petit peu: en particulier, la liste des tags se déroule jusqu'à la fin de l'écran au lieu de passer à la ligne au bout du div. Également, ma section de login n'a pas l'air de prendre le style arrondi que les autres sections ont bien l'air de traiter.

Mais ce sont des détails. L'important est que Médoc soit enfin utilisable. Ouf!

Médoc dans les choux

Ces petits coquins de chez Ocsigen/Eliom, le framework de developpement Web basé sur Ocaml et qui fait fonctionner Médoc, ont fait de sérieuses mises à jour, que je n'ai pas suivies aussi assidument que je l'aurais dû... Résultat des courses, mon Médoc est par terre, et je ne peux plus accéder à mes documents, ce qui est plutôt ennuyeux pour une application qui se targue de gérer lesdits documents. C'est d'autant plus rageant que mon idée initiale, qui était de protéger les documents en les collant directement dans la base sous forme chiffrée plutôt qu'en vrac dans un dossier, protège maintenant trop bien.

J'ai découvert (un peu tard, et après m'être fait pas mal de cheveux) la page du changelog, et je suis en train de m'atteler à porter tout ça.

dimanche 26 août 2012

Oeuf de canard - Un graphe de scène en OCaml

Je ressors une vieillerie, légèrement dépoussiérée: j'avais posté il y a bien longtemps une vidéo d'une planète terre tournoyant, mais je n'avais pas partagé le code. C'est maintenant fait, et mon graphe de scènes en OCaml est maintenant dans Git. C'est assez trivial, si ce n'est quelques difficultés au niveau du calcul du frustum et de l'héritage en OCaml (j'utilise les fonctionnalités objet).

C'est suffisant pour s'amuser, même si un parseur dans un format 3D standard ne serait pas de trop (j'avais commencé avec les fichiers Wavefront .obj). OpenGl a cependant beaucoup évolué depuis, donc c'est très old school.

dimanche 22 janvier 2012

Un client Twitter en OCaml

Pourquoi s'arrêter en si bon chemin? Voici un nouvel œuf de canard: le même client Twitter pour l'API stream, mais cette fois-ci en OCaml. Utilisant Yojson et OCurl, c'est le port copie conforme du client C++.

Si jamais je me décidais à enregistrer les messages dans une base de données, c'est plutôt à partir du client OCaml que je continuerais.

dimanche 13 novembre 2011

OCaml détecté

Yay, Github a manifestement refait tourner les statistiques, et détecte bien maintenant que Médoc est fait à 45% d'OCaml.

jeudi 22 septembre 2011

Le crible en détail, partie 2

Continuons donc à décortiquer le programme.

La fonction principale de notre crible contient la sous-fonction do_sieve, qui permet une fois de plus de devenir tail recursive, ou à récursion terminale en bon français.

Elle prend comme paramètres l'entier courant à tester, l'ensemble des multiples des nombres premiers précédents, et la liste courante des nombres premiers. À partir de là, c'est tout bête: l'on retire le premier élément de nos multiples, et on le compare à l'entier à tester. S'ils sont égaux, notre entier courant est un multiple, donc non premier, l'on ré-appelle donc notre fonction récursive avec l'entier suivant. Si ce n'est pas l'entier à tester, on a trouvé un nombre premier! L'on ajoute tous ses multiples à la liste des multiples, l'on rajoute l'entier à la liste de nos nombres premiers, et l'on appelle la fonction récursivement. Et bien sûr, puisqu'il faut bien sortir un jour de la récursion, n'oublions pas notre condition de sortie: lorsque l'entier courant atteint le maximum passé en paramètre de la fonction principale, l'on retourne nos nombres premiers.

Notez l'optimisation qui consiste à zapper les multiples de 2: l'on démarre à 3, et l'on passe de 2 en 2 pour l'entier courant et pour le calcul des multiples.

Voici une indication des performances. Loin des meilleurs résultats impératifs, mais tout à fait utilisables.




LimiteTemps
100 000140ms
1 000 0002.7s
10 000 00041s

samedi 17 septembre 2011

Le crible en détail, partie 1

Mon post précédent était un poil laconique, je vais donc reprendre quelques détails d'implémentation, et en profiter pour vous convaincre que OCaml, ça roxe.

Les premières lignes sont simplement la définition d'un set sur les entiers. Les sets en OCaml fonctionnent via des modules paramétrés par d'autres modules suivant une certaine signature. L'on créé donc un module définissant un type t et sa comparaison, avec lequel on paramétrise le module Set.Make.


module OrderedInt =
struct
type t = int
let compare = compare
end

module IntSet = Set.Make(OrderedInt)

Ceci va donc définir un module tout neuf, que j'appelle IntSet, et qui fournit des fonctions pour travailler sur des sets fonctionnels, c'est à dire sans effets de bords. Ainsi, ajouter un élément au set retourne en fait un nouveau set contenant l'élément. C'est plus efficace qu'il n'y parait, et surtout, cela permet de raisonner à plus haut niveau, d'une manière plus mathématique.

Ensuite, définissons une fonction qui nous sera utile plus tard, qui retourne une liste d'entiers entre deux entiers définis, avec un pas.

let seq_step a b t =
let rec do_seq_step a acc =
if a > b then
acc
else
do_seq_step (a + t) (a::acc)
in
List.rev (do_seq_step a [])

La fonction définit une sous-fonction do_seq_step, prenant l'entier courant a, et la liste d'entiers accumulés jusque là acc. La récursion est triviale: si mon entier courant a atteint ma borne supérieure, retournons la liste accumulée jusque là. Sinon, appelons récursivement la fonction, avec mon entier courant incrémenté du pas, et ma liste à laquelle j'aurais ajouté mon entier courant.

Depuis la fonction externe, l'on appelle la sous-fonction en passant la borne inférieure, et une liste vide. Enfin, parce que l'on a accumulé les entiers à l'envers (en partant de 3 de 2 en 2: [3], puis [5; 3], puis [7; 5; 3]...), l'on inverse la liste avec List.rev. Le résultat:

# seq_step 3 10 2;;
- : int list = [3; 5; 7; 9]

Notez que l'on aurait pu implémenter la fonction d'un coup, de cette façon:

let rec seq_step2 a b t =
if a > b then
[]
else
a::(seq_step2 (a + t) b t)

Le résultat semble strictement le même, et pourtant:

# seq_step 1 1000000 1;;
- : int list =
[1; 2; 3; 4; 5; 6; 7; 8; 9; ...]

# seq_step2 1 1000000 1;;
Stack overflow during evaluation (looping recursion?).

La deuxième version est arrêtée nette par un dépassement de pile. C'est que la première version est une récursion terminale (ou tail-recursion en anglais), c'est à dire qu'elle peut être trivialement transformée en une fonction itérative, ce que fait le compilateur. La deuxième, elle, utilise la pile comme accumulateur implicite, et provoque donc un dépassement de pile lorsque le nombre de récursions devient trop important.

Pour des raisons de performances et d'extensibilité, il est donc recommandé d'implémenter ses fonctions de manière récursive terminale à chaque fois que cela est possible.

Voilà, suivant l'approche ascendante qui sied bien à OCaml et aux langages fonctionnels, c'est à dire la construction de fonctions de base, puis de fonctions plus haut niveau utilisant ces briques, les utilitaires utilisés pour le crible. Dans la deuxième partie, je reprendrait plus en détails l'algorithme du crible lui-même.

Implémentation fonctionnelle d'un crible d'Ératosthène

Voilà l'engin:


module OrderedInt =
struct
type t = int
let compare = compare
end

module IntSet = Set.Make(OrderedInt)

let seq_step a b t =
let rec do_seq_step a acc =
if a > b then
acc
else
do_seq_step (a + t) (a::acc)
in
List.rev (do_seq_step a [])

let sieve n =
let rec do_sieve current multiples primes =
if current > n then
primes
else if IntSet.is_empty multiples || IntSet.min_elt multiples <> current then
do_sieve
(current + 2)
(List.fold_left
(fun acc e -> IntSet.add e acc)
multiples
(seq_step (current * 3) n (current * 2)))
(current::primes)
else
do_sieve (current + 2) (IntSet.remove current multiples) primes
in
do_sieve 3 IntSet.empty [2]

Sur ma machine, compilé en natif, trouver les nombres premiers en dessous du million prend 2,7 secondes.

mercredi 14 septembre 2011

Un peu de fonctionel

Je me suis remis à Project Euler, et résolu quelques problèmes supplémentaires. Je continue à tenter d'apporter des solutions aussi fonctionnelles pures que possible, ce qui m'a amener à réfléchir à un crible d’Ératosthène fonctionnel pur. Jusqu'ici, j'utilisais les fonctionnalités impératives d'OCaml pour m'en tirer un avec un gros tableau de booléens, mais en fait, si l'on considère un set d'entiers pour les multiplies, il est facile de faire quelque chose de plutôt efficace. Je dois mettre un peu d'ordre dans mon code, et je le posterai ici.

lundi 16 mai 2011

XSD - Pas évident

Assez moches, finalement, les schémas XSD. Documentation absconse, grammaire peu claire, et bien trop de façons de générer la même chose.

C'est que j'essaie de reprendre le bon vieux mlxsd et de le rendre un peu plus respectueux des standards. Et c'est pas de la tarte.

lundi 2 mai 2011

Ocaml Lwt - Mini tutorial

Connaissez-vous Lwt? C'est une bibliothèque proposant un modèle asynchrone autour des fibres, ou threads légers (déjà qu'un thread était un processus léger, est-ce qu'une fibre est un processus vachement léger?) coopératifs, c'est à dire que la fibre va s'exécuter jusqu'à ce qu'elle laisse le champ libre à une autre fibre, via des appels spéciaux, par exemple pour effectuer des opérations d'entrée sortie, ou attendre un résultat fourni par une autre fibre.

Je trouve le manuel un poil léger, donc voici une série d'exemples très simples pour se faire une idée de l'utilisation du bazar. L'idée est généralement de créer un thread lwt, puis de le faire tourner via un appel à Lwt_main.run jusqu'à ce que l'exécution du thread soit complétée, et d'en récupérer une valeur de retour.

Dans un terminal ocaml, ces commandes permettent de charger les bibliothèques lwt:


#use "topfind";;
#require "lwt";;
#require "lwt.extra";;

C'est parti. L'exemple le plus simple: ce bout de code créé un bête thread lwt déjà terminé, contenant la valeur 3. Le Lwt_main.run attend que le thread soit complété et en retourne la valeur. Dans ce cas, il n'a rien à faire que de simplement retourner 3.
let t = Lwt.return 3 in
Lwt_main.run t

Un premier exemple faisant enfin quelque chose: l'on utilise la fonction Lwt_unix.sleep, lequel retourne un thread qui se termine au bout d'un certain temps. Cette fonction va donc dormir pendant 2 secondes avant de retourner unit.
let t = Lwt_unix.sleep 2. in
Lwt_main.run t

Voici l'utilisation d'une routine d'entrée sortie. Parce que les threads sont coopératifs, il est nécessaire de ne jamais bloquer ou utiliser des fonctions d'entrée / sortie non bénies par lwt. L'on utilisera donc Lwt_io pour lire et écrire des fichiers ou les entrées sorties standard. Le modèle est le même: notre thread écrit "Miaou" sur la sortie standard, et le thread est complété.
let t = Lwt_io.write_line 
Lwt_io.stdout
"Miaou"
in
Lwt_main.run t

Ah, la fonction Lwt.bind! Elle permet de chaîner les threads: quand un thread est complété, il passe son résultat au suivant, qui s'exécute à son tour. Dans notre cas, le plus simple, nous passons unit à la fonction qui écrit. Vous l'aurez deviné, ce bout de code attend 2 secondes, puis affiche "Miaou" à l'écran.
let t = 
Lwt.bind
(Lwt_unix.sleep 2.)
(fun () -> Lwt_io.write_line Lwt_io.stdout "Miaou")
in
Lwt_main.run t

Enfin, un exemple démontrant les capacités asynchrones de Lwt. Dans cet exemple, l'on créé 3 threads qui chacun attendent un certain temps avant d'afficher un message. La fonction Lwt.join retourne une fois que les 3 threads sont complétés. Quelque soit l'ordre dans lequel les threads ont été passés à la fonction, ils s'afficheront bien en temps voulu.
let sleep_and_print duration message = 
Lwt.bind
(Lwt_unix.sleep duration)
(fun () -> Lwt_io.write_line Lwt_io.stdout message)
in
let t = Lwt.join
[sleep_and_print 3. "3 secondes";
sleep_and_print 4. "4 secondes";
sleep_and_print 2. "2 secondes"]
in
Lwt_main.run t


Voilà les bases, à partir de là il ne s'agit plus que de chaîner les fonctions d'entrées sorties Lwt décrites dans le manuel.

J'ai l'intention d'essayer d'écrire un petit moteur asynchrone orienté événements, permettant à l'utilisateur de souscrire dynamiquement à un certain nombre d'entrées, comme par exemple une horloge, des entrées réseau, ou des passages de messages (probablement pas pour OpenRailz, mais on ne sait jamais...).

Le mot de la fin: OCaml ne permet pas l'exécution concurrente de code car il possède, tout comme Python d'ailleurs, un verrou global. Lwt ne change pas cette règle, et n'est donc pas orienté vers du "number crunching", par exemple, mais permet en revanche de designer de manière très élégante des applications réactives devant gérer des événements complexes. Les appels coopératifs s'assurent que l'application n'est jamais en train d'attendre alors qu'un autre bout de code pourrait être exécuté, ce qui permet d'aller taper dans la base de données, écouter des messages réseau, lire des fichiers, et logger dans d'autres d'une manière transparente et claire. Cool non?

mercredi 6 avril 2011

Postgresql et Ocaml: performances à l'insertion

Postgresql permet 2 types d'insertions: les insertions en pur SQL à l'aide d'un "insert", et les copies. À quel point les copies sont-elles plus efficaces que les insertions?

Voici quelques tests rapides via le mode immédiat d'Ocaml, en passant par le wrappeur Postgres. Ouvrons tout d'abord une connexion.


ocaml -I /usr/lib/ocaml/postgresql/ -I /usr/lib/ocaml/threads
# #load "unix.cma";;
# #load "threads.cma";;
# #load "bigarray.cma";;
# #load "postgresql.cma";;
# let connectionstring = "host=localhost dbname=Test";;
# let c = new Postgresql.connection ~conninfo:connectionstring ();;

Voici quelques fonctions de base qui servent respectivement à créer une séquence de a à b, et à mesurer le temps pour exécuter une fonction donnée. Notez que "seq" est un peu alambiquée pour permettre la tail-recursion (et donc de demander des sequences de 1M d'éléments sans dépassement de pile).

# let seq a b =
let rec do_seq a r =
if a > b then r else do_seq (a + 1) (a::r)
in List.rev(do_seq a []);;
# let time f e =
let t1 = Unix.gettimeofday() in f e;
let t2 = Unix.gettimeofday() in
t2 -. t1;;

Et enfin, les deux fonctions à tester, l'une faisant tourner n insertions dans une transaction, l'autre utilisant la copie.

# let run n =
ignore(c#exec "begin");
List.iter
(fun e -> ignore(c#exec
~params:[|string_of_int e; "def"; "0.143"|]
"insert into data values($1, $2, $3)"))
(seq 1 n);
ignore(c#exec "commit");;
# let batch n =
ignore(c#exec "copy data from stdin");
List.iter
(fun e -> ignore(c#putline ((string_of_int e)^"\tdef\t0.143\n")))
(seq 1 n);
ignore c#endcopy;;

Ces fonctions font strictement la même chose, c'est à dire insérer pour i de 1 à n, {i, "def", 0.143} dans une table ayant pour colonnes un entier, une chaîne, et un double.

L'on fait tourner...


# time run 100000;;
- : float = 13.3781638145446777
# time batch 100000;;
- : float = 0.458184957504272461


Ce qui nous donne un déjà très honorable 7500 insertions par seconde avec des insert, et un ahurissant 220000 insertions par seconde avec une copie. Les gains sont encore plus significatifs avec plus de lignes, la copie atteignant 240000 insertions par seconde pour 1 million d'éléments.

Notez tout de même que ma base est configurée avec fsync = off afin d'éviter les coûteuses synchronisations avec le disque. Quand bien même il est vrai qu'attendre que tout soit écrit aplanirait les différences, un cas d'utilisation standard d'insertions massives est la jointure avec une table temporaire pour faire un select, par exemple, pour lequel les écritures disques ne rentreront pas en compte.

Ce qui nous fait donc quand même une insertion par copie 30 fois plus rapide qu'une insertion en SQL pur.

dimanche 11 octobre 2009

Médoc - Stable

Médoc est fini! Enfin, c'est un bien grand mot, mais disons qu'il est maintenant à un stade où il est utile pour moi, ce qui n'est déjà pas si mal. En dehors de fonctions de recherche plus avancées au niveau de l'interface web, qui seront aisées à ajouter, la partie la moins jolie est sans doute la petite application pour enregistrer le document dans la base. Puisque l'ensemble du projet est maintenant hébergé par OcamlCore, j'ai voulu écrire l'interface en utilisant Lablgtk, qui ne se prête pas vraiment à l'écriture d'une petite appli rapide, qui n'est qu'en fait qu'un front-end pour les programmes scanimage et convert, qui font le gros du boulot. Ce n'est donc ni très beau, ni très robuste.



L'interface web, elle, est utilisable, efficace (du moins pour un nombre réduit d'utilisateurs simultanés), et le système de tag fonctionne gentiment. J'ajoute presque chaque courrier administratif reçu au système, maintenant. On verra à l'usage!

mardi 22 septembre 2009

mlBlocks

J'ai vérifié de nouveau que mon vieux clone de Tetris en Ocaml marchait encore. Ce fut également l'occasion pour mettre quelques captures sur la section Web de la forge Ocaml.

Ocaml n'est certainement pas prêt pour les blockbusters en 3D, mais pour coder de petits jeux, il est très certainement à la hauteur!



lundi 21 septembre 2009

Le système de tags

Voilà, le système de tags est ajouté. L'on peut insérer de nouveaux tags, ou les supprimer d'un clic. Pour les tags qui n'existent pas encore, il est possible de taper leur nom directement.



Ce fut une belle plongée dans les CSS, et je suis maintenant à peu près satisfait de l'apparence générale de l'application. Il me reste maintenant à améliorer l'ajout de nouveaux documents, qui repose pour l'instant sur un script un petit peu fragile.

vendredi 11 septembre 2009

Redirections à travers Ocaml

Mes remerciements les plus vifs à un japonais inconnu dont le post(?) sur un blog(?) m'a débloqué! J'essayais de gérer les redirections entre des programmes démarrés à travers Ocaml, et j'avais un mal fou à rediriger les flux. Je n'ai rien compris à son post, juste vu le code, et j'ai compris que je m'y prenais comme un pied.

Maintenant, j'ai une fonction toute bête pour prendre un JPEG en entrée et ressortir le texte en sortie:


let gocr file =
let i_file = Unix.openfile file [Unix.O_RDONLY] 0 in
let gocr_in, jpg_out = Unix.pipe() in
let result, gocr_out = Unix.pipe() in
let pid0 =
Unix.create_process
"jpegtopnm"
[|"jpegtopnm"|] i_file jpg_out Unix.stderr in
Unix.close jpg_out;
let pid1 =
Unix.create_process
"gocr"
[|"gocr"; "-"|] gocr_in gocr_out Unix.stderr in
Unix.close gocr_out;
ignore(Unix.waitpid [] pid0);
ignore(Unix.waitpid [] pid1);
let buffer = Buffer.create 4096 in
let read_buffer = String.create 512 in
let r = ref true in
while !r do
let len = Unix.read result read_buffer 0 512 in
if len > 0 then
Buffer.add_substring buffer read_buffer 0 len
else
r := false
done;
Buffer.contents buffer


L'on ouvre donc le fichier d'entrée, un pipe entre jpegtopnm et gocr, et un pipe entre gocr et notre sortie (si quelqu'un a une idée pour se débarrasser du deuxième pipe, je suis preneur!). L'on chaîne les processus avec les descripteurs de fichiers ainsi récupérés, et, très important, on ferme le pipe d'output après l'avoir donné à un processus. Pour la fin, je lis bêtement depuis mon pipe jusque dans un buffer, jusqu'à ce que le pipe soit vide, et je renvoie le contenu du buffer.

Ceci m'approche d'autant plus du moment où je vais enfin pouvoir faire la reconnaissance de caractères directement depuis le serveur web.

mardi 1 septembre 2009

Ocsigen - Images dynamiques

Voilà un sujet sur lequel il m'a fallu batailler ferme! C'est tout l'intérêt d'Ocaml: la difficulté est d'arriver à écrire du code qui compile. Une fois que le compilo est content, le code fonctionnera comme convenu. C'est exactement ce qui s'est passé avec mes images dynamiques.

Les documents et miniatures sont tous sauvés dans la base de données de Médoc. Quand bien même balancer ce genre de gros blobs à travers du SQL n'est pas franchement recommandé, je pense qu'il s'agissait là d'un des cas de figure autorisés. En effet, sauver ces images dans la base permet de centraliser toutes les données, ce qui simplifie la sauvegarde et la restauration et permet de laisser à la base le soin de chiffrer les données.

Cela veut dire qu'il faut pouvoir générer une image à partir d'un buffer binaire récupéré de la base. Ça tombe bien, Ocsigen sait envoyer du binaire via un Streamlist. Écrivons par exemple un service Ocsigen qui affiche une image à partir d'une chaîne binaire:


(* Petites déclarations ennuyeuses *)
open Lwt
open XHTML.M
open Eliom_services
open Eliom_parameters
open Eliom_sessions
open Eliom_predefmod.Xhtml

(* Déclaration du service, appelé "view", et qui prend *)
(* l'identifiant entier de l'image en paramètre *)
let view = Eliom_services.new_service ["view"] (int "id") ()

(* Initialisation du service *)
let _ = Eliom_predefmod.Streamlist.register view
(fun sp id () ->
return
([fun () ->
return (Ocsigen_stream.of_string (get_data id))],
"image/png"))

Il suffira d'écrire la fonction get_data qui prend dans notre cas l'identifiant entier de l'image à afficher, et qui retournera la chaîne contenant les données binaires de l'image.

Ce service peut ensuite être utilisé dans la déclaration d'un élément img:
 <img src="view?id=2"/>
pour afficher des images à l'intérieur d'une page (attention aux performances quand même, quand on va taper dans la base pour chaque image!).

lundi 31 août 2009

Premiers pas avec Ocsigen

Je voulais m'y mettre depuis longtemps, à ce serveur web. Or, justement, j'avais un petit projet en tête pour me délasser d'AdH: un système de documentation.


Ocsigen est donc un serveur web écrit en Ocaml, qui charge des modules basés sur le framework Eliom. Ocsigen est performant, mais surtout force un typage extrêmement fort du HTML en lui-même, et des liens entre les différents services. Enfin, l'on pourra réutiliser tout le code Ocaml existant, à condition de le faire rentrer dans le modèle de thread, basé sur une bibliothèque de threads coopératifs, Lwt.

Le gros problème d'Ocsigen, c'est qu'en dehors de la documentation, point de salut, ou si peu.

La documentation en elle-même est complète, mais manque furieusement d'exemples si l'on sort un tant soit peu des sentiers battus, et il faut pas mal d'efforts pour commencer à comprendre la manière dont les différents éléments s'enchaînent. L'inférence de types de nos amis Hindley et Milner vient heureusement à notre secours, et il serait une très mauvaise idée d'essayer d'écrire les interfaces avec des types aussi biscornus.

La communauté, elle, est inexistante (ou du moins, très bien cachée), et les sites publics qui tournent sous Ocsigen sont rares. Heureusement, un Wiki a été écrit pour Ocsigen pour le Google Summer of Code, ce qui aide beaucoup à voir comment, par exemple, utiliser Postgresql à travers Lwt.

Il est particulièrement intéressant d'écrire des pages à travers Ocsigen, c'est une manière d'appréhender la programmation Web tout à fait différente. On finit par se faire aux types ésotériques, et j'ai rapidement écrit quelques fonctions pour faciliter l'utilisation d'éléments récurrents.

Mon système de documentation, répondant au doux nom de Médoc (Mes docs :) ), avance tout gentiment, en quelques jours j'ai déjà une base de données (avec chiffrage des données les plus sensibles!), un outil en ligne de commande pour y sauver les documents, et une page web qui permet d'effectuer des recherches et d'afficher les documents. En voici une petite capture:



La page affiche tous les documents correspondant à un terme, en se basant sur le "Full Text Search" de Postgresql. L'on affiche également les miniatures de chaque page, cliquables et qui mènent au document grandeur nature.

Un peu de CSS sera nécessaire pour rendre l'ensemble plus joli, mais le système est déjà utile, ce qui est un bon début!