69j 04h 09m 09s


h1 9/25/2006 08:14:00 AM

Je me suis dit que 69 jours était un uptime satisfaisant pour relancer Dolebraï après quelques modifications mineures dans le traitement des metadatas et la description du flux auprès d'icecast.

Le développement de liquidsoap n'a pas avancé depuis des semaines, autant du côté des bugfixes que des nouvelles features qu'on a en tête. J'ai tenté d'attirer de nouveaux utilisateurs sur notre nouvelle release, mais malgré quelques pics de traffic/downloads je crois que c'est plutôt un échec. Trop compliqué pour les besoins simples de la plupart des gens? Ou alors faut attendre, on est pas pressé..

Mes annonces sur la Caml-list, la liste Icecast (mais on n'est toujours pas dans la liste des clients), et Freshmeat. Visiblement ça s'est répercuté sur un truc inconnu, Softpedia/Linux.

Générateurs


h1 9/19/2006 10:48:00 AM

Ça faisait longtemps que je voulais m'essayer à camlp4, je me suis donné pour exercice d'implémenter des générateurs à la Python -- ce n'est pas nouveau, il me semble avoir déja vu passer ça sur la liste Caml. Le résultat final fait plaisir.

let c = mkseq [-102;1;5;10;33;42;77] ;;
let s = mkseq [1;0;-1] ;;

list_of s*x for x in c if x#even for s in s ;;
= [-42;0;42;-10;0;10;102;0;-102]
list_of x*s for s in s for x in c if x#even ;;
= [-42;-10;102;0;0;0;42;10;-102]
table_of (x,x#even) for x in c ;;
= {77:false;42:true;33:false;10:true;
5:false;1:false;-102:true}

are_all x#even for x in c ;;
= false
is_any s#null for s in s ;;
= true
max_of x#abs for x in c if x#even) ;;
= 102

Comme vous pouvez le voir j'ai orienté objet, car c'est un aspect de Caml que j'essaie d'explorer plus à fond ces temps-ci.

Venons en directement aux conclusions avant de passer aux morceaux de code:
  • Camlp4 c'est puissant, mais faut tâtonner pour s'en sortir au début, avec un oeil sur le manuel. De façon générale je suis sympathique à l'idée d'un préprocesseur qui s'assume en tant qu'outil de meta-programmation, plutôt que des capacités vicieuses d'introspection.
  • Les générateurs sont facilement réalisés comme un sucre syntaxique à base de fold. Je ne sais pas pourquoi Python utilise des itérateurs/coroutines, yield & co., peut-être parce que l'utilisation de clôtures n'y est pas efficace. En Caml je ne vois aucun problème à mon approche, rien qui justifierait l'ajout du concept de coroutine ici. J'aime à voir ici une réponse à un post de Guido où il annonce la suppression de map, iter, reduce (le fold de Python) et lambda: pour lui reduce est incompréhensible et inutile, pour moi c'est habituel et fondamental. Là où Guido marque un point c'est qu'ayant cette syntaxe naturelle, je ne suis pas sûr de vouloir utiliser map ou iter directement si j'ai le sucre syntaxique à disposition.
  • Je suis fan du sous-typage structurel pour les objets dans Caml, mais l'inférence échoue et renvoie des erreurs cryptiques. C'est loin d'être intuitif et j'ai dû sévèrement annoter mes méthodes. C'est encore pire si on essaie d'exprimer le schéma [.. for x in ..] comme une fonction. Il faut alors spécifier le type des objets itérables et générables, et effectuer des coercions explicites vers ces types, ce qui est piégeux à cause de la récursivité du type.
  • Les listes et autres types de base en OO, c'est moche, surtout quand on imagine ces valeurs omniprésentes se trimballer leur collection de méthodes triviales tout du long.

Pour vous avouer le fond de ma pensée, j'aimerais trouver/écrire un langage de script (au sens de la facilité d'utilisation, pas de l'absence de compilation) typé statiquement avec sous-typage structurel pour des objets et des variants polymorphes à la OCaml, avec une bonne inférence qui rende le tout utilisable par Monsieur Toulmonde. J'aimerais voir ce genre de concept se répandre. Je me doute que le problème de l'inférence n'est pas simple, j'essaierai d'étudier ça de plus près à l'occasion.

Le code



Mon fichier ML définit quelques classes qui pourraient faire partie de la lib standard OO s'il y en avait une -- même si ça fait peur, disons qu'on imagine. À la fin, j'y utilise la notation que je ne définis que plus bas, mais c'est pour présenter le type des méthodes
fold. La longueur vient uniquement du choix OO, et du fait que je définis plusieurs
itérateurs (seq et table) et générateurs (seq, table, max, conjunction et disjunction mais pas iter).

(* Fichier geno.ml *)

class boolean (b:bool) =
object
method in_val = b
method to_s = if b then "true" else "false"
end

class conjunction (b:bool) =
object
inherit boolean b
method add (x:boolean) = new conjunction (x#in_val && b)
end

class disjunction (b:bool) =
object
inherit boolean b
method add (x:boolean) = new disjunction (x#in_val || b)
end

class integer (i:int) =
object
method in_val = i
method to_s = string_of_int i
method times (y:integer) = new integer (i*y#in_val)
method abs = new integer (abs i)
method null = new boolean (i = 0)
method even = new boolean (i mod 2 = 0)
end

class max (i:int) =
object
inherit integer i
method add (x:integer) = new max (max i x#in_val)
end

class ['a] seq (l:'a list) =
object
method add x : 'a seq = new seq (x::l)
method fold : 'b. 'b -> ('b -> 'a -> 'b) -> 'b =
fun x0 f -> List.fold_left f x0 l
method to_s =
"[" ^ (String.concat ";" (List.map (fun s -> s#to_s) l)) ^ "]"
end

class ['k,'v] table (l:('k*'v)list) =
object
method add x : ('k,'v) table = new table (x::l)
method get k = List.assoc k l
method fold : 'b. 'b -> ('b -> 'k -> 'b) -> 'b =
fun x0 f -> List.fold_left (fun acc (k,v) -> f acc k) x0 l
method to_s =
"{" ^
(String.concat ";" (List.map (fun (k,v) -> k#to_s^":"^v#to_s) l)) ^
"}"
end

let mkseq l = new seq (List.map (fun i -> new integer i) l)
let print x = Printf.printf "%s\n%!" x#to_s
let ( * ) x y = x#times y

let _ =
let c = mkseq [-102;1;5;10;33;42;77] in
let s = mkseq [1;0;-1] in

print (list_of s*x for x in c if x#even for s in s) ;
print (list_of x*s for s in s for x in c if x#even) ;
print (table_of (x,x#even) for x in c) ;

print (are_all x#even for x in c if x#even) ;
print (is_any s#null for s in s) ;
print (max_of x#abs for x in c if x#even)

Maintenant je définis l'extension de syntaxe qui traduit les constructions type générateurs vers la bonne recette à base de fold (ce qu'on parcourt) et add (ce qu'on construit).

(* Fichier pa_geno.ml *)

open Pcaml

let genseq = Grammar.Entry.create gram "genseq" ;;

EXTEND
genseq: [
[ "for" ; x=patt ; "in" ; c=expr ; gs=genseq ->
fun body -> let g = gs body in <:expr<
(fun acc -> $c$#fold acc (fun acc $x$ -> $g$ acc))
>>
| "if" ; test=expr ; gs=genseq ->
fun body -> let g = gs body in <:expr<
(fun acc -> if $test$#in_val then $g$ acc else acc)
>>
| -> fun body -> <:expr< (fun acc -> acc#add $body$) >> ]
] ;
END ;;

EXTEND
expr: BEFORE "expr1" [ "genexpr" NONA
[ "list_of" ; fx=expr ; gs=genseq ->
let g = gs fx in <:expr< $g$ (new seq []) >>
| "table_of" ; fx=expr ; gs=genseq ->
let g = gs fx in <:expr< $g$ (new table []) >>
| "are_all" ; fx=expr ; gs=genseq ->
let g = gs fx in <:expr< $g$ (new conjunction True) >>
| "is_any" ; fx=expr ; gs=genseq ->
let g = gs fx in <:expr< $g$ (new disjunction False) >>
| "max_of" ; fx=expr ; gs=genseq ->
let g = gs fx in <:expr< $g$ (new max 0) >>
]
] ;
END ;;

Enfin pour compiler le tout:

>camlp4o pa_extend.cmo q_MLast.cmo pa_geno.ml -o pa_geno.ppo
ocamlc -I `camlp4 -where` -c -impl pa_geno.ppo
camlp4o ./pa_geno.cmo geno.ml -o geno.ppo
ocamlc -I `camlp4 -where` -c -impl geno.ppo
ocamlc geno.cmo -o gen

Pour afficher la traduction du fichier ML par le préprocesseur, c'est camlp4o ./pa_geno.cmo pr_o.cmo geno.ml et on y lit que max_of x#abs for x in c if x#even devient:

c#fold (new max 0) (* for x in c *)
(fun acc x ->
if x#even#in_val then (* if x#even *)
acc#add x#abs (* x#abs *)
else acc)

Je suis sûr que ça change vos perspectives pour la journée :p

Netvibes


h1 9/15/2006 03:30:00 AM

Toutes mes données ne sont pas chez Google, mes flux RSS préférés ainsi que quelques notes et TODO list sont sur ma page d'accueil Netvibes, la startup française à la mode. Et quand je décide de trainer sur MSN c'est désormais dans mon browser via Netvibes, grâce au module Meebo.. Oublions les problèmes de vie privée que tout cela pose, en tout cas c'est cool.

Récemment j'ai ajouté le module Daily Cartoons, je découvre Dilbert, Reality Check, ou encore Pearls Before Swine. Je ris, parfois jaune:
pearls before swine

Portées de variables


h1 9/12/2006 03:58:00 AM

Encore un post de geek: devinerez-vous le résultat du petit bout de code suivant?

var bla = ["oof","rab"] ;
var fun = [] ;
for (var i in bla) {
// i varie de 0 à 1
var inside = 2*i ;
fun[i] = function () {
return (inside+"-"+bla[i]) ;
} ;
// (2*3)+"-"+"a" == "6-a"
}
alert("La reponse est: "+fun[0]()+"...") ;

Et le résultat du suivant?

var bla = ["oof","rab"] ;
var fun = [] ;
function fill(i) {
var inside = 2*i ;
fun[i] = function () {
return (inside+"-"+bla[i]) ;
} ;
}
for (var i in bla) { fill(i) }
alert("La reponse est: "+fun[0]()+"...") ;

Lancer le test?

A priori vous vous êtes trompés, et ce n'est pas votre faute mais celle du langage.

L'expérience fonctionne aussi en Python, ça me déçoit de ces deux langages, je pensais que les problèmes de liaison de variables y étaient joliment résolus, comme l'exemple avec une fonction le montre.

Mais qu'est-ce qui se passe, en fait ? Avec une boucle, une seule instance des variables "i" et "inside" est crée, elle persiste d'une étape de l'itération à l'autre. Du coup au moment où on évalue la fonction fun[0], "i" et "inside" ont leur dernière valeur, respectivement 2 et "rab". A la limite pour "i" on dira que c'est spécifié comme cela, mais pour "inside"? La variable est déclarée dans la boucle! Si on veut avoir une variable persistant d'une itération à l'autre, on la déclare hors de la boucle. De même si on veut pouvoir accéder à "i" après la fin de la boucle. Si ce n'est pas l'effet voulu il est naturel de déclarer dans la corps de la boucle, et on voudrait bien que cela change quelquechose!

Edit: la "suite" de cette note est ici.

Strong typing doesn't have to suck


h1 9/11/2006 03:52:00 AM

Au détour de conversations je suis tombé aujourd'hui sur cet article. L'article dresse une très brève histoire des langages de programmation, et des systèmes de type. Les programmeurs des débuts s'arrachent les cheveux, confondant les adresses de nombres avec celles de chaînes. Ils inventent les systèmes de type de C/Pascal/etc: statiques (vérification à la compilation, plus d'information de type à l'exécution) et forts (une variable d'un certain type ne peut contenir de valeurs d'un autre type). Ces systèmes sont peu expressifs et somme toute mal foutus, ce qui mène à deux nouvelles évolutions. La puissance des machines aidant, on invente d'un côté les langages interprétés avec leurs systèmes de type dynamiques (le contenu d'une variable peut changer de type) très en vogue de nos jours. Mais le typage statique n'est pas mort, il a évolué dans les ML et Haskell: plus expressif, il relève de vraies erreur de programmation, et ne requiert pas d'annotations de la part de l'utilisateur. Conclusion: le typage fort ne suce pas forcément des ours.

L'intéret de la présentation est qu'elle s'adresse à un large public de programmeurs, et non pas à des gens qui connaissent déja un ML. Des exemples simples de code sont donnés pour expliquer et motiver. Bref, je pense que ça peut intéresser un paquet de gens: ceux qui ne connaissent pas ML, et peut-être ceux qui en sont dégoutés; ceux qui en ont marre du typage dynamique, ou des annotations de type, et ceux qui ne le savent pas encore.

Malheureusement ce n'est pas un guide introductif à ML, et je n'en connais pas que je puisse recommander. Si seulement OCaml était simple à apprendre, le monde saurait..

Voyage en Australie, en chiffres


h1 9/06/2006 02:48:00 AM

Je suis bien arrivé en Australie. Parti dimanche à 23h15 de Paris, je suis arrivé à Singapour à 17h45 heure locale. Pour la première fois j'ai eu droit à des turbulences, mais j'ai même pas eu peur, fort de la statistique d'Estelle: un homme qui passerait toute sa vie en avion aurait plus de chance de mourir de vieillesse que d'un accident -- pour les problèmes de santé liés à la bouffe et aux nuits dans un fauteuil, on sait pas.



Quand je suis reparti à 19h50 pour Sydney, il faisait déja bien nuit. Après un second vol à 10000m d'altitude et 1000km/h, notre avion est descendu vers l'Océanie et une vitesse d'aterissage de 300km/h. Je suis arrivé à 05h10 sur le sol Australien, et mon passage aux douanes n'a pris que 10 secondes. La température extérieure est de 11°C, contre 30°C à Singapour: de ce côté là de l'équateur on se refroidit en descendant au Sud, et c'est la fin de l'hiver.



A 7h00 je quitte Sydney (4 200 000 hab) pour Canberra (325 000 hab, capitale de l'Australie, 20 000 000 hab) à bord d'un tout petit avion mu par ses hélices, et peuplé de costards et tailleurs. Je ne lis pas le journal Australien qu'on m'a proposé, la couverture me suffit pour bien rigoler quand je pense à la remarque du Lonely Planet: contrairement au cliché les Australiens ne sont pas des aventuriers chasseurs de crocodiles, etc.



La fin du premier épisode de cette histoire se termine à mon arrivée à Canberra à 7h50 heure locale, mardi matin. A ce moment là il est 23h50, mardi soir en France. Cela fait donc 24 heures que je suis parti, et pas beaucoup moins d'heures en vol, ce qui fait mal à mon karma par rapport à la dernière fois où je l'ai calculé.


PAR CATÉGORIE HECTARES GLOBAUX
ALIMENTATION 1.4
TRANSPORTS 0.8
LOGEMENT 0.6
BIENS ET SERVICES 1.5
VOTRE EMPREINTE TOTALE 4.3

EN COMPARAISON, L'EMPREINTE ECOLOGIQUE MOYENNE
DANS VOTRE PAYS EST 5.3 HECTARES GLOBAUX
PAR PERSONNE.

IL Y A 1.8 HECTARES GLOBAUX PRODUCTIFS PAR PERSONNE
DANS LE MONDE. SI TOUT LE MONDE VIVAIT COMME VOUS
ON AURAIT BESOIN DE 2.4 PLANETES


Ah.. vous vouliez des photos d'où j'habite, des paysages et tout ? Patientez.