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).
- LLB
- 00:46
- > Lien permanent
- > Commentaires
- > Abus ?


![[Jeu] Ideo](images_/carre1.gif)

