novembre
2009
Bonjour !
Voici, en avant-première, quelques-uns des changements attendus pour la prochaine version majeure d’OCaml, à savoir la 3.12. Qu’on se le dise : s’il y a matière à écrire un billet, c’est que les changements ne sont pas mineurs !
La dernière version majeure publiée (version 3.11) apportait surtout de nouveaux outils (dynlink accessible en natif et refonte conjointe de camlp4, ocamlbuild). La prochaine version promet quant à elle au moins un changement conceptuel assez remarquable. Lequel ? Bah, je fais commencer par parler d’autre chose pour garder le suspense…
Une dose de sucre syntaxique
Comme chaque version, OCaml 3.12 apportera sa dose de sucre syntaxique. Deux ajouts ont retenu mon attention. Le premier concerne l’ouverture locale des modules, qui était déjà disponible sous forme d’extension camlp4 :
let open String in
concat (make 1 ' ') ["foo"; "bar"]
On pourra aussi utiliser la syntaxe alternative :
let _ = String.(concat (make 1 ' ') ["foo"; "bar"])
Le second ajout concerne le type record, et vise surtout à alléger les notations à la création et lors du filtrage :
let make foo bar foobar barfoo = { foo; bar; foobar; barfoo }
let get_foo { foo = n; _ } = n
Ces deux changements mineurs devraient apporter plus de souplesse. Ils s’inscrivent dans la continuité d’allègements déjà réalisés auparavant. Je pense à la version 3.08, qui a introduit une fonctionnalité peu utilisée : la possibilité de ne préciser le nom du module que dans un champ du record :
struct
type t = {foo : int; bar : int; foobar : int; barfoo : int}
end
let create x y z t = { Foo.foo = x; bar = y; foobar = z; barfoo = t }
Bon, c’est bien gentil tout ça, mais ça n’est pas non plus une révolution, et il n’y a pas vraiment matière à en parler avant la sortie de la prochaine version majeure d’OCaml. Nous allons donc maintenant lever le voile sur le changement conceptuel que j’annonçais en début de billet.
Des modules de première classe
Voilà la raison d’être de ce billet : la future version d’OCaml introduira les modules de première classe (dans le genre de ceux de Moscow ML, sauf erreur de ma part). Pour les non-initiés qui liraient ce billet, cela signifie que les modules peuvent désormais être reçus en argument et/ou renvoyés comme résultat d’une fonction. Voici un petit exemple pour vous donner une idée de la syntaxe qui a été retenue (attention : la fonction print ne compile pas avec la version de développement utilisée au moment de la rédaction de ce billet) :
sig
val get_name : unit -> string
val get_path : unit -> string
val exists : unit -> bool
val is_directory : unit -> bool
end
let read () =
Array.map (fun x ->
let module This =
struct
let get_name () = Filename.basename x
let get_path () = Filename.dirname x
let exists () = Sys.file_exists x
let is_directory () = Sys.is_directory x
end
in (module This : FILE)
) (Sys.readdir ".")
let print =
List.iter begin fun mdl ->
let module (This : FILE) = mdl in
Printf.printf "%s: est un %s\n%!" (This.get_name ())
(if This.is_directory () then "répertoire" else "fichier")
end
Au niveau des signature, on aura quelque chose de semblable à ceci :
val print : (module FILE) array -> unit
Voilà qui ouvre de bien sympathiques perspectives…
D’autres changements sont prévus, mais je ne vais pas non plus tout vous dire. Si vous êtes intéressés par tout ce que j’ai passé sous silence, n’hésitez pas à consulter le dépôt et la version en cours de développement !
À bientôt,
Cacophrène
Pour la notation { foo = n; _ }, j’aurais dû préciser qu’un nouvel avertissement va être ajouté (warning R) pour pointer l’utilisation de motifs de record qui ne reprennent pas tous les champs. L’écriture let get_foo { foo = n } = n va donc devenir en quelque sorte obsolète (ou, tout au moins, ne sera plus recommandée). Je pense que c’est pour rendre les choses plus cohérentes et mentionner explicitement qu’on a opéré une « sélection » dans les champs filtrés.
Les modules de première classe : peut-être que la demande viendra avec l’offre… en tout cas c’est clair qu’une nouveauté comme celle-là ne va pas s’apprivoiser comme ça. Faudra voir dans quelle mesure c’est pratique, et si ça vaut le coup de s’en servir à la place d’autres solutions déjà éprouvées. Pour ma part j’ai quelques codes qui utilisent des record improbables qui me semblent de bon candidats pour la conversion. Mais peut-être qu’à l’utilisation ça va se révéler désastreux.
Au sujet du code mort : j’ai l’impression que le développement d’OCaml est actuellement plus orienté vers l’ajout de nouvelles fonctionnalités. Tout ce qui relève de près ou de loin de l’analyse du code et de la compilation est un peu laissé de côté.
Merci.
let make foo bar foobar barfoo = { foo; bar; foobar; barfoo } <br />
Hourra, ça va grandement m’alléger l’écriture dans OCaml-Idaho
Ça n’a l’air de rien mais à mon avis cette simplification justifiait à elle seule un billet.
let get_foo { foo = n; _ } = n <br />
Quelle différence par rapport à let get_foo { foo = n} = n ?
Pour les modules de première classe je n’en ai jusqu’ici jamais senti le besoin en OCaml. Par contre en Coq ça manque cruellement.
En fait j’aurais préféré une élimination efficace du code mort. C’est ça qui empêche l’utilisation à grande échelle du système de modules: il y a trop de code mort dans l’exécutable final.