vendredi 6 février 2015
ksprintf et le petit scarabée

On gagne toujours à flâner dans la documentation...

Un jour, le maître vient voir le petit scarabée pour lui poser une question.

Le maître:

Petit scarabée, j'ai cette fonction prenant une chaîne de caractères et l'envoyant soit vers un fichier, soit dans stderr, soit ailleurs, enfin bref je l'utilise pour garder des traces pour le débogage. Par défaut, elle écrit sur stderr:

# let print_debug = ref prerr_endline ;;
val print_debug : (string -> unit) ref = {contents = <fun>}

Dans mon programme, je ne veux pas l'utiliser directement, mais je veux avoir une fonction debug qui prend un format et une chaîne et qui passe à print_debug le résultat de l'application de cette chaîne à ce format. Je voudrais donc pouvoir écrire:

debug "str = %s" "ma chaine"

Peux-tu me proposer le code de la fonction debug ?

Le petit scarabée:

C'est facile ! Il suffit de construire la chaîne avec Printf.sprintf et passer ensuite le résultat à print_debug, comme ça:

# let debug fmt s = !print_debug (Printf.sprintf fmt s);;
val debug : ('a -> string, unit, string) format -> 'a -> unit = <fun>
# debug "str = %s" "ma chaine";;
str = ma chaine
- : unit = ()
Le maître:

Bien bien, et maintenant si je veux passer un format utilisant un entier ?

Le petit scarabée:

C'est déjà possible, Maître, puisqu'il suffit de passer un autre format et le type de l'argument s de la fonction sera alors contraint. Regardez, on peut déjà le faire:

# debug "x = %d" 12;;
x = 12
- : unit = ()
Le maître:

D'accord. Bon, je voudrais maintenant pouvoir donner deux arguments en plus du format, que me proposes-tu?

Le petit scarabée:

Eh bien, je ne peux pas faire une fonction prenant un nombre inconnu de paramètres:

# debug "%s = %d" "x" 12;;
Error: This function has type
         ('a -> string, unit, string) format -> 'a -> unit
       It is applied to too many arguments; maybe you forgot a `;'.

Alors je dois faire une fonction supplémentaire, que j'appelle debug2:

# let debug2 fmt p1 p2 = !print_debug (Printf.sprintf fmt p1 p2);;
val debug2 : ('a -> 'b -> string, unit, string) format -> 'a -> 'b -> unit =
  <fun>
# debug2 "%s = %d" "x" 12;;
x = 12
- : unit = ()
Le maître:

Ah. Et si je veux pouvoir utiliser un format à trois arguments ?

Le petit scarabée:

Là encore, je dois faire une fonction supplémentaire, que j'appelle debug3:

# let debug3 fmt p1 p2 p3 = !print_debug (Printf.sprintf fmt p1 p2 p3);;
val debug3 :
  ('a -> 'b -> 'c -> string, unit, string) format -> 'a -> 'b -> 'c -> unit =
  <fun>
# debug3 "%s = %d/%d" "x" 1 2;;
x = 1/2
- : unit = ()
Le maître:

Tout cela est bien lourd, ne trouves-tu pas ?

Le petit scarabée:

C'est sûr ! Mais que faire ? Connaissez-vous une solution, cher Maître ?

Le maître:

Il en existe une, en effet. Regarde bien la documentation du module Printf. Il y a dedans une fonction appelée ksprintf:

# Printf.ksprintf;;
- : (string -> 'd) -> ('a, unit, string, 'd) format4 -> 'a = <fun>

Vois-tu comme elle ressemble à Printf.sprintf ?

# Printf.sprintf;;
- : ('a, unit, string) format -> 'a = <fun>

Comme le dit la documentation, au lieu de retourer la chaîne finale après application des arguments au format, la chaîne est passée à la fonction donnée en premier paramètre. Comprends-tu comment elle pourrait t'être utile pour notre problème ?

Le petit scarabée:

Je crois que oui, Maître. Tout devient simple, avec une unique fonction debug:

# let debug fmt = Printf.ksprintf !print_debug fmt;;
val debug : ('a, unit, string, unit) format4 -> 'a = <fun>
# debug "str = %s" "ma chaine";;
str = ma chaine
- : unit = ()
# debug "x = %d" 12;;
x = 12
- : unit = ()
# debug "%s = %d/%d" "x" 1 2;;
x = 1/2
- : unit = ()
Le maître:

Très bien. Mais dis-moi, pourquoi ne définis-tu pas debug simplement ainsi:

let debug = Printf.ksprintf !print_debug;;
Le petit scarabée:

Cela dépend, Maître, si la fonction réféfencée par print_debug est appelée à changer après la définition de debug. Avec ma définition, le fait que debug prenne en paramètre fmt avant d'appliquer Printf.ksprintf à !print_debug fait que !print_debug sera bien évalué à chaque appel, donc utilisera bien la dernière fonction affectée.

Le maître:

C'est très bien, petit scarabée. A l'avenir, n'hésite pas à parcourir la documentation régulièrement, même si tu connais par cœur les fonctions que tu utilises. Tu y trouveras parfois des fonctions précieuses. D'ailleurs, il existe également dans le module Printf les fonctions kbprintf et kfprintf.

Le petit scarabée:

Merci, Maître.

Thèmes:
Mots-clés: