Liens


h1 2/17/2010 06:20:00 PM

Quelques lectures qui se font écho, dans l'air du temps. Je ne m'inquiète pas trop de certaines craintes vis-a-vis d'Internet, du genre "Google nous rend bête, l'homme n'utilise plus sa mémoire, n'écrit plus". Globalement, j'ai grandi avec et je me sens pas trop con. Par contre, je suis plus sensible à d'autres remarques sur la façon dont l'Internet évolue. Ce n'est pas que de la nostalgie, et il ne s'agit même pas seulement d'Internet: la politique, la créativité et la culture, la SF vs. le fantastique et le mystique, le rapport au monde.

Sur Commondreams, The Information Super-Sewer:
“The crowd phenomenon exists, but the hive does not exist,” Lanier told me. “All there is, is a crowd phenomenon, which can often be dangerous. To a true believer, which I certainly am not, the hive is like the baby at the end of ‘2001 Space Odyssey.’ It is a super creature that surpasses humanity. To me it is the misinterpretation of the old crowd phenomenon with a digital vibe. It has all the same dangers. A crowd can turn into a mean mob all too easily, as it has throughout human history.”

Sur écrans.fr, Fuck Google:
Nous utilisons Google, Gmail, Youtube, ses calendriers, documents, cartes, etc. Nous les utilisons comme les WC publics quand nous devons aller pisser ; parce qu’il semble qu’il n’y ait pas d’autre option. Or il existe des alternatives. [...] Web 3.0 : le grand repli sur vos propres serveurs.

Et aussi Le Web, c'est folk:
Les amateurs d’aujourd’hui préfèrent la « fantasy », les elfes et les animaux étranges aux thèmes de science-fiction très populaires au début du Web  : « L’idée de construire un vrai futur en ligne a été remplacée par une échappatoire vers un monde imaginaire. » [...] Les usagers sont « autorisés » à télécharger leur production sur de nombreux services différents, ce qu’on faisait déjà il y a quinze ans mais sur son propre serveur. Cette vision cynique des usagers se perpétue, ils sont divertis, mais également exploités en tant que producteurs de contenus et cliqueurs de publicités. Le « nuage informatique » va encore diminuer leur rôle au profit de puissants ordinateurs centralisés. C’est l’opposé de l’idée de peer to peer où les utilisateurs construisaient des systèmes en connectant leurs ordinateurs.

Grant Petersen à propos de sa compagnie de bicyclettes, Rivendell:
Simple things make people feel smart, or at least competent, and complication has the opposite effect. If people feel smart and competent, they’re happy, and happy people are nice to other people, and it all starts or stops with how hard it is to use something.

Pour la route, une brochette d'artisanat:
  • Super pingouins, un bon vieux site, qui me touche au moins moi pour les bons souvenirs.
  • Liquidsoap, un outil de streaming surpuissant, développé lentement mais sûrement depuis 2003. C'est du logiciel libre développé en toute indépendance (sauf la pression des utilisateurs), même si c'est hébergé par le monstre Sourceforge. Mais le mieux, c'est les projets plus ou moins fous que les gens construisent avec cet outil! (On devrait vraiment s'occuper de rassembler une meilleure page de liens.)
  • Dolebraï, qui ne permet toujours pas à la foule de voter pour la musique qu'elle aime (en fait, c'est une feature militante) et qui est bien plus petit que Free Music Archive dans le même esprit.

Libellés :

Quel effet ça fait


h1 2/08/2010 08:04:00 PM

Quelle différence entre a→b→c et a→b→c? Pour le savoir, lisez ce qui suit.

De temps en temps, je regarde un article scientifique, ou un bout de code, et je n'en suis pas satisfait. Un truc me chiffonne, ça peut se réparer, mais ya autre chose qui cloche, et au fond c'est tout bancal, ça n'en finit pas, et il vaut mieux tout remettre à plat. Ceci est arrivé récemment dans liquidsoap avec le module de décodage de fichier. Ce n'est pas intéressant de détailler, il y a juste un point dont je voulais parler. Mais avant, je trouve que c'est important de dire qu'il faut remettre les choses à plat de temps en temps.

Donc, il s'agit de décodage. Première question: qu'est ce que c'est un décodeur? J'ai mis un peu de temps à essayer plusieurs styles, pour finalement décider input -> buffer -> unit, une fonction qui prend en entrée une méthode pour lire des données, et un buffer pour écrire le résultat du décodage, et qui ne renvoie rien mais remplit le buffer un peu plus à chaque fois qu'on l'appelle.

A côté de cette notion très générique on a des notions plus spécialisées comme le type file_decoder = { fill : Frame.t -> unit ; close : unit -> unit }. C'est un enregistrement qui contient une fonction de remplissage de flux (on lui donne une frame (un morceau de flux) à remplir) et une fonction de fermeture/nettoyage où l'on libère les ressources allouées pour le décodage.

Ceci étant décidé, j'écris un bout de code générique qui emballe un décodeur pour construire un décodeur_de_fichier:
let file_decoder filename decoder =
let input = input_from_file filename in
let buffer = create_buffer () in
let fill frame =
while not_enough_data_in buffer do
decoder input buffer
done ;
fill_frame_from_buffer frame buffer
in
{ fill = fill ; close = fun () -> close input }

Vous me suivez? Maintenant, j'implémente un décodeur, pour le format MP3 en utilisant la bibliothèque mad:
let create_decoder input =
let resampler = create_resampler () in
let mad_stream = Mad.openstream input in
(fun buffer ->
let data = Mad.decode_frame_float mad_stream in
let sample_freq,_,_ = Mad.get_output_format fd in
let content,length =
resampler ~audio_src_rate:(float sample_freq) data
in
put_audio_in_buffer buffer content length)

PAF! Vous voyez le bug? Je parie que non, en tout cas moi je l'avais raté. J'ai réussi à ne pas être d'accord avec moi même, penser a→b→c ici et a→b→c là!...

Le problème, c'est les effets. En mathématiques, une fonction prend un argument et renvoie un résultat. On ne sait pas comment ça se passe "dedans", en tout cas ça n'interagit pas sur le "dehors", et ça se passe pareil à chaque fois: même entrée, même sortie. En informatique, c'est bien plus compliqué. La fonction interagit avec le monde, elle peut afficher quelquechose à l'écran, elle peut aller chercher un résultat sur internet, dans un fichier, ou simplement dans une case mémoire qu'elle partage avec d'autres fonctions. On est ainsi habitué à avoir tout un paquet de fonctions de type a→b, puisqu'il ne s'agit pas seulement de prendre un objet de type a pour calculer un objet de type b mais aussi potentiellement de se livrer à tout un tas d'interactions avec le monde.

Mon code utilise cela, mais se prend les pieds dedans. Ce qui est joli, c'est que j'ai une "solution". Mais voyons d'abord le problème. Notre décodeur prend un canal d'entrée (input), un canal de sortie (buffer), et est supposé avoir comme effet de lire un peu de données en entrée, de les convertir et les écrire en sortie. Il ne renvoie rien d'utile (unit), tout son interet réside dans l'effet; si on l'appelle un assez grand nombre de fois, on finit par avoir assez de données dans notre buffer -- c'est ce qu'on a fait plus haut. On peut cacher un certains nombres d'informations dans le décodeur, c'était mon intention, par exemple j'y ai alloué un resampler pour convertir les données vers la bonne fréquence d'échantillonage: cet outil doit être (et est bien) crée une fois et une seule pour chaque décodeur.

Ce resampler maintient un état interne, tout comme le décodeur mad (mad_stream). Allouer ces objets est aussi un effet! Mais à quel moment a-t-il lieu? Le type ne l'indique pas: dans input->buffer->unit, après quel argument un effet peut-il avoir lieu? Dans le code de file_decoder j'utilise un décodeur comme si le seul effet était le décodage, qui a lieu une fois qu'on a renseigné l'input et l'output. Mais dans le code du décodeur MP3, pas le choix, je m'autorise un effet entre le moment où on m'a donné l'input et le moment où on me donne le décodeur. Résultat, quand on utilise file_decoder avec le create_decoder MP3, on a un son tout haché, car le décodeur et le resampleur sont reinitialisés sans arrêt, ce qui provoque la perte d'une partie des données mémorisées dans leurs buffers internes.

Ces problèmes sont très vicieux, et sont toujours un sujet de recherche active. Mais concrètement, que peut-on y faire avec les outils d'aujourd'hui? Documenter, espérer que tout le monde se comprend? Pas terrible, j'ai réussi à être en désaccord avec moi-même sur une courte période de temps. Comme d'habitude, ce serait bien si le système de type nous servait de garde fou.

En logique, la notion de focalisation (focusing) est liée à la question des effets. Une formule logique est vue comme un jeu entre deux joueurs: un qui prouve l'autre qui réfute, ou encore, l'environnement qui fournit une entrée (argument) et la machine qui renvoie une sortie (valeur de retour). Les connecteurs logiques sont attribués à l'un ou à l'autre joueur: dans (int*int)→(int*int) c'est d'abord l'environnement qui donne un argument, directement composé de deux entiers; puis la machine calcule un résultat, directement composé de deux entiers. Ici je dis directement, car on ne peut pas demander une réponse partielle à la machine, tout ceci vient d'un coup, une unique réponse à une seule question. La dynamique associée au type int→int→(int*int) est exactement la même: il n'y a pas deux questions à l'environnement, mais une seule, les deux entiers en entrée arrivent d'un coup. Là dedans, les seuls effets ne peuvent donc se situer qu'à l'interface entre les deux joueurs, dans le calcul qui se passe entre une question et une réponse.

Commençons à redescendons sur terre. Pour introduire la possibilité d'un effet, en focalisation, on peut introduire un délai. Par exemple, si on veut une paire d'entiers paresseuse (dont le contenu n'est calculé que si nécessaire), on retarde le calcul des int: (unit->int)*(unit->int). Dans l'autre sens, on peut aussi vouloir retarder le moment où un argument est passé, par exemple avec a->(unit*(b->c))... c'est ce qu'il nous faut!

On la refait avec un délai autour du décodeur, implémenté non pas comme (unit*...) mais plus agréablement avec un type variant:
type decoder = Decoder of (buffer -> unit)
type file_decoder = input -> decoder

let file_decoder filename decoder =
let input = input_from_file filename in
let buffer = create_buffer () in
let Decoder f = decoder input in
let fill frame =
while not_enough_data_in buffer do
f buffer
done ;
fill_frame_from_buffer frame buffer
in
{ fill = fill ; close = fun () -> close input }

let create_decoder input =
let resampler = create_resampler () in
let mad_stream = Mad.openstream input in
Decoder (fun buffer ->
let data = Mad.decode_frame_float mad_stream in
let sample_freq,_,_ = Mad.get_output_format fd in
let content,length =
resampler ~audio_src_rate:(float sample_freq) data
in
put_audio_in_buffer buffer content length)

Le code du décodeur MP3 n'a changé que d'un iota, mais cela suffit à forcer sa bonne utilisation dans file_decoder, c'est à dire à passer l'input une fois pour toute, et ne plus passer que le buffer dans les appels suivants. Vraiment? Non, à vrai dire, on peut toujours se prendre les pieds dedans, ne serait-ce que parce que c'est possible de traduire entre le vieux type de décodeur et le nouveau, en violant ainsi la logique qu'on a tenté de forcer. Mais en pratique, ce petit garde fou pousse à faire naturellement la bonne chose, ou au moins à se poser la question.

Tout est bien qui fini bien. Cette histoire pourrait aussi s'intituler "à quoi diable pourrait bien servir un type variant avec un seul constructeur?" Ou encore, "c'est fou comme de belles idées théoriques ont du sens même en dehors de leur strict cadre théorique."

Libellés : , ,