blog2geek.com
LLBAvatar de LLB

38 billets | Profil

Recherche Google

ce blog tous
Derniers billets Connexion
Archives

test

07/08/2007

Pourquoi F#, plutôt qu'OCaml ? (Partie 2)

Pourquoi F#, plutôt qu'OCaml ? (Partie 2)

 

Dans la première partie, j'avais évoqué plusieurs points assez généraux : environnement de développement, l'utilisabilité sous Windows, les bibliothèques disponibles et l'avenir des langages. J'avais aussi parlé de la surcharge, présente dans F#. Je vais continuer sur quelques points techniques et détailler certaines fonctionnalités de F#. Dans cette partie, je m'attarderai plus sur les fonctionnalités de F# que sur les faiblesses de Caml.

À ce jour, je n'ai toujours pas trouvé de sites français parlant de F#. Considérez donc celui-ci comme le premier et n'hésitez pas à poser des questions s'il y a des choses par claires. Je serais ravi de détailler.

 

Généricité

La généricité est un point très important des langages de
programmation. C'est la principale innovation de C++ par rapport au C,
via son mécanisme de templates.

F#, héritant de .NET, possède le concept d'interfaces. C'est certes
moins souple que les templates de C++ ou que le typage dynamique des
langages interprétés, mais cela permet également d'obtenir une
certaine généricité.

En Caml, les types liste et tableau sont totalement indépendants. Il
n'est pas possible, au nom du typage fort et de l'absence d'objets
(OCaml permet la POO, mais elle est quasiment absente de la
bibliothèque standard), d'écrire une fonction générique travaillant
sur une collection. Une fonction calculant la moyenne des éléments
d'une liste devra être réécrite pour utiliser les tableaux.

En F#, on utilise le type IEnumerable (nommé aussi Seq). La fonction suivante peut ainsi être appelée avec un tableau, une liste, une liste paresseuse, une liste générée à partir d'une fonction...
let mean c = Seq.fold (+) 0 c / Seq.length c;;

De même, une fonction travaillant sur une collection de caractères acceptera les types "char list", "char array", "string", etc.

 

Typage dynamique

Quelques fois, on souhaite passer outre le système de typage. Parfois,
on regrette les langages à typage dynamique. En F#, c'est possible
d'obtenir ainsi plus de liberté et de déporter le typage à l'exécution
(bien sûr, c'est plus risqué, donc on l'utilise prudemment). Par
exemple, on peut définir une liste d'éléments quelconques :
let a = [box 4; box 3.2; box 'c'; box "e"]

On peut aussi définir des fonctions qui ne seraient pas typables dans
un système à la Hindley-Milner. Par exemple, la fonction suivante
renvoie : pour n = 0, une liste vide ; pour n = 1, une liste de liste
vide ; pour n = 2, une liste de liste de liste vide, etc.

let rec gen = function
| 0 -> box []
| n -> box [gen (n - 1)]

 

Introspection

En complément du typage dynamique, il est possible d'obtenir à l'exécution des informations de types sur toutes les valeurs. On peut donc regarder ce qu'il y a dans une valeur convertie par la fonction box. On peut aussi faire du pattern matching sur le type d'une valeur. On peut inspecter la liste des méthodes d'un objet et ainsi de suite. On peut même générer du code à l'exécution et construire des classes à la volée. Parce que les langages interprétés n'ont pas le monopole du dynamisme, F# propose, grâce à .NET, des mécanismes d'introspection assez poussés. Et malgré cela, F# possède à peu près la même sûreté et presque les mêmes performances que Caml.

 

Compréhensions de listes

Parce que la manipulation des listes et des tableaux est très fréquente, F# propose du sucre pour faciliter leur écriture. Les compréhensions de listes sont une technique élégante pour effectuer les opérations map, concat, filter ainsi que des conversions (liste vers tableau et inversement). Il est aussi possible d'utiliser des intervalles, par exemple "2..20" ou "'a'..'z'".

Comparez (F#) :

let f str = [for c in str when c > 'a' ->> [c; c]]

Avec (OCaml, après avoir défini String.to_list) :

let f str =
let s = String.to_list str in
let l1 = List.filter (fun c -> c > 'a') s in
let l2 = List.map (fun c -> [c; c]) l1 in
List.concat l2

 

Affichage générique

J'en ai déjà parlé plusieurs fois, il est possible d'afficher
n'importe quel type en F#, avec la fonction print_any. Par défaut, il
affiche la structure interne du type (pour les types somme, tuples,
structures...), mais il est possible de la redéfinir.

Je vous renvoie à mon autre article, sur l'affichage générique en F#.

 

Objet

F# arrive à concilier les programmations objet et fonctionnelle. Ainsi, un type somme est un objet qui peut avoir des méthodes. Par ailleurs, les types de base peuvent être considérés comme des objets. Ils possèdent quelques méthodes par défaut et il est même possible d'ajouter des méthodes aux entiers. Par exemple :

type System.Int32 with
member x.Square = x * x
member x.Abs = abs x

Ce code ajoute les méthodes Square et Abs à tous les int. Par ailleurs, les active patterns sont une solution innovante permettant d'utiliser le pattern matching sur n'importe quel type complexe. J'avais déjà parlé des active patterns en F#.

 

Syntaxe

La syntaxe de F# est vraiment très concise. L'utilisation de l'indentation (optionnelle) apporte vraiment un plus au niveau de la lisibilité, même lorsque l'on imbrique du pattern matching.

type Foo(x) =
member f.Square = x * x

Ce code définit une classe Foo, avec un constructeur prenant un entier x en argument, qui possède une méthode Square renvoyant le carré de x. Et dans votre langage, ça ressemble à quoi ?

En moyenne, du F# est à peu près aussi concis que du Ruby, et plus concis même que du Python. La concision d'un langage est souvent témoin d'une grande expressivité. Contrairement à ce que l'on voit en Java ou en C++, chaque ligne de code de F# est véritablement utile et fait quelque chose. On n'écrit pas du code, juste parce qu'il le faut. La surcharge, la réutilisation du code, les compréhensions de listes, la bibliothèque et l'utilisation fréquente de l'objet réduisent énormément la taille du code par rapport à OCaml (qui est pourtant considéré comme concis parmi les langages à typage statique).

> Rédiger un commentaire

22:50 30/10/2007 - inconnu

Salut !
Bof... la seule idée que le F# soit associé à Microsoft est pour moi un frein majeur à son utilisation (philosophique en premier lieu, sans entrer dans la programmation elle-même).
 Pour ce qui est de la concision du langage, j'apprécie la juste mesure : un code trop dense est difficile à lire, car chaque ligne demande de nombreux efforts à son lecteur. À l'inverse, un code trop long est difficile à comprendre parce qu'il noie le lecteur. OCaml offre à mon goût un bon compromis entre ces deux excès.
 La compréhension des listes doit bien pouvoir être faite avec Camlp4, sauf erreur. Et même si ce n'est pas le cas, c'est surtout une affaire d'habitude ; je ne les utilise pas, et ça ne m'empêche pas d'utiliser des fonctionnelles comme map, iter ou fold.
 L'environnement de développement : gedit et make font très bien l'affaire sous GNU/Linux, pour ne citer qu'eux. Ce n'est pas très esthétique... et les beaux environnements sont beaux, certes, et après ?
Les exemples avec Int64... quand on en fait un usage intensif, on groupe toutes les fonctions dans un module avec open Int64 et on redéfinit des opérations si besoin l'est. On règle les éventuels conflits de nom en explicitant le nom du module. Aucun ennui pour moi.
OK pour les nouveautés du filtrage, encore que là encore... Camlp4 doit bien nous permettre de faire des choses (difficilement ?) assez sympathiques.
Enfin, point essentiel, les deux langages ne sont pas comparables sur leurs bibliothèques dans la mesure où OCaml sort d'un labo et n'a pas accès aux codes .NET déjà développés.
Bref, le F# est un peu comme le café instantané. C'est une heureuse imitation du café, avec des avantages immédiats, mais hélas, ce n'est pas du café. 
A bon entendeur,
Topologist

23:12 30/10/2007 - LLB

Salut, et merci pour le commentaire.
 
Je comprends très bien le frein psychologique. J'ai eu le même au début, mais le langage m'a convaincu et je suis passé au delà (surtout que les sources sont disponibles du compilateur sont disponibles).
 
Je te propose surtout d'essayer le langage par toi-même. Après l'avoir essayé réellement, je peux t'assurer que c'est désagréable de repasser à Caml. La syntaxe est plus concise, mais tout aussi lisible. Ce qui rend plus concis, c'est surtout la bibliothèque (on recode beaucoup moins de choses simplistes) et l'utilisation de l'indentation pour définir les blocs. Un détail comme le "in" à la fin des lignes devient vite lourd.
 
F# possède beaucoup de nouvelles fonctionnalités, et cela en fait un nouveau langage à part entière. Je suis d'accord, ce n'est pas Caml, mais ce n'est pas non plus une "simple imitation". Je suis d'accord, ce n'est pas le même café, mais il est tout aussi bon (légèrement meilleur ou légèrement pire, selon les goûts).
 
Pour le Int64, tu te rends rapidement compte que c'est bordélique, même avec un open. Il faudrait redéfinir les opérateurs (+), (-), etc. mais c'est très dangereux.
 
Camlp4 peut faire beaucoup de chose, mais il est peu utilisé en pratique (sauf pour les besoins spécifiques). En pratique, tu ne feras pas de compréhensions avec Camlp4, tu chercheras à te débrouiller, tant bien que mal, avec les fonctions de base. Les compréhensions apportent concision *et* lisibilité. Regarde mon exemple, la différence est assez nette... surtout que Caml ne possède pas la fonction String.to_list. Recoder une fonction si commune relève plus de la perte de temps...
 
Enfin, l'un des points les plus importants me semble être la généricité, car elle apporte beaucoup en réutilisation du code et en factorisation. Et ça, tu ne peux pas l'avoir en Caml, même avec de la bonne volonté et du Camlp4. Ce qui fait gagner beaucoup de temps pour le débogage, c'est l'affichage générique. printf "%A" affiche une valeur, que que soit son type. C'est un truc qui manque vraiment à Caml, malgré tous les workaround que l'on trouve sur le net.

08:34 31/10/2007 - inconnu

Salut !
 Fort heureusement, ce ne sont pas des inconvénients majeurs. Les fonctions manquantes constituent une remarque récurrente. Pour ma part, j'ai pris l'habitude de grouper ce dont j'ai besoin dans un module à part indiqué au compilateur avec l'option -I. Bien sûr, il faut le faire et c'est plus long que si c'était déjà fait... mais au pire il y a le projet extlib, qui règle notamment le problème de String.to_list, qui se résout d'ailleurs en deux lignes si on peut se passer de récursivité terminale :
 let list_of_string x =
    Stream.npeek (String.length x) (Stream.of_string x)
 Par contre il y a des choses qui ne me plaisent pas chez Microsoft (indépendamment du langage). Par exemple, le site consacré au F# propose une présentation générale au format ppt, la licence est une licence Microsoft et pas une simple (L)GPL, etc. Cela ne m'empêchera pas d'essayer F#, mais je suis a priori sceptique.
Cordialement,
Topologist

17:18 08/01/2008 - inconnu

En fait, la compréhension, ça fait un certain temps qu'elle existe en OCaml (enfin, en Camlp4), elle n'est juste pas activée par défaut. Avec un petit coup de Google, on peut aussi trouver des versions étendues de cette comprehension, cf. http://dutherenverseauborddelatable.wordpress.com/downloads/comprehension-f
or-ocaml/
 
 Du coup, l'extrait
let f str = [for c in str when c > 'a' ->> [c; c]]
devient
let f str = List.concat [ [c;c] | (String) c <- str ; c > 'a' ]
C'est peut-être un chouillème moins concis, mais rien de choquant.  Ah, et à ce prix-là, la syntaxe s'applique aussi aux flux, aux tableaux et aux listes paresseuses.
 
 De même, les Active Patterns existent aussi en OCaml (une extension Camlp4, toujours), à l'intérieur de Micmatch http://groups.google.com/group/fa.caml/browse_frm/thread/738ab503c18ccc06/7
da7bf7cbc87c0c2#7da7bf7cbc87c0c2 .
Quant aux énumérables, ils apparaissent depuis quelques bonnes années dans ExtLib.
 
Voilà. Non mais des fois :) 

18:37 08/01/2008 - LLB

J'avais déjà répondu aux arguments avec Camlp4 (qui est un très bon outil). Les compréhensions sont très peu utilisées en général ; elles ne sont pas standard et requièrent une bibliothèque externe. Les utilises-tu souvent ? Mais dans tous les cas, elles restent très pauvres par rapport à ce que F# offre en standard (cf yield, expressions monadiques, etc.).
 
Et merci pour le lien sur les compréhensions. C'est mieux que ce que j'avais vu (tu remarqueras en passant qu'il a été publié après mon article).
 

23:12 23/02/2008 - inconnu

Les langages fonctionnels sont majoritairement utilisés pour coder des parsers ou des compilateurs. Caml égale presque les performances de C. Qu'en est il de celles de F# ? Les langages .NET sont incroyablement plus lents. Et alors ? Et alors, quand un mec passe 15 jours à compiler son système, il apprécie de minimiser les temps de compilation. Qu'en penses tu ?
 Kreeg

00:42 24/02/2008 - LLB

Sous Windows, les performances de F# sont proches de celles de Caml. Sous Unix, c'est légèrement plus lent (certaines optimisations ne sont pas implémentées dans Mono), mais ça reste très correct. À titre indicatif, voici un benchmark (F# a été un peu amélioré depuis) :
http://cs.hubfs.net/forums/thread/3207.aspx
 
Pour le temps de compilation, F# est un peu plus lent que Caml. Mais, avec Visual Studio, toutes les erreurs de compilation (noms des identifiants, typage, etc.) sont vérifiées au moment où on tape le code. Ce que l'on perd d'un côté, on le gagne de l'autre.

> Rédiger un commentaire