Introduction▲
La grammaire de F# est relativement simple et consistante. Il n'y a que deux types d'éléments : les expressions et les déclarations. Quand une expression est évaluée, elle se simplifie en une valeur simple. Toutes les valeurs ont un type (et un seul).
Si vous avez accès au mode interactif de F# (fsi.exe), je vous conseille de l'essayer. Dans ce cours et les suivants, beaucoup d'exemples sont donnés. Je vous recommande de tester ces exemples par vous-même pour mieux comprendre. Dans le mode interactif, l'invite de commande est représentée par un chevron <. Il faut taper ;; suivi d'entrée pour envoyer la commande à l'interpréteur. L'interpréteur évalue ensuite la commande. Si la commande est une expression, elle est simplifiée. La valeur de retour est affichée, ainsi que son type.
Pour se familiariser avec la syntaxe, voici des exemples d'expressions. En général, ça devrait assez simple à comprendre. Les commentaires en F# sont :
- // commente jusqu'à la fin de la ligne
- (* commente jusqu'au *) associé (ça peut être imbriqué)
Dans les exemples qui suivent, j'ai utilisé des valeurs de base et quelques opérateurs. On verra les fonctions dans un prochain cours.
I. Les Types de base▲
I-1. Les Entiers▲
> 42;;
val it : int = 42Le type de l'expression "42" est donc int (entier). Sa valeur est "42".
> 5 + 5;;
val it : int = 10Le type de l'expression "5 + 5" est donc int. Sa valeur est "10".
> 4 * (5 - 3);;
val it : int = 8
> (5 + 6) % 10;; // % correspond au modulo.
val it : int = 1I-2. Les Flottants▲
> 4.;;
val it : float = 4.0
> 4.0;;
val it : float = 4.0
> 4.5;;
val it : float = 4.5
> 4.2 + 5.3 * 4.1;; // + fonctionne aussi sur les flottants
val it : float = 25.93
> 2. ** 8.;; // ** est l'opérateur puissance.
val it : float = 256.0I-3. Les Caractères▲
> 'a';;
val it : char = 'a'
> '\n';; // saut de ligne
val it : char = '\n'
> '\'';;
val it : char = '\''
> ''';; // raccourci syntaxique
val it : char = '\''I-4. Les Booléens▲
> true;;
val it : bool = true
> false;;
val it : bool = false
> 4 = 5;; // en C/C++ : ==
val it : bool = false
> 4 < 42;;
val it : bool = true
> 4 <> 42;; // en C/C++ : !=
val it : bool = true
> 'a' < 'b';;
val it : bool = true
> true || false;;
val it : bool = true
> 3 < 4 && 'c' > 'a';; // && a une faible priorité.
val it : bool = trueI-5. Les Chaînes de caractères▲
> "test";;
val it : string = "test"
> "Hello " + "world!";; // concaténation
val it : string = "Hello world!"
> "test".[0];; // Accès à un caractère (ça commence à 0)
val it : char = 't'
> "test".[1];;
val it : char = 'e'
> "c:\\games\\";;
val it : string = "c:\\games\\"
> @"c:\game\";; // chaine "verbatim" : les \ ne sont pas interprétés
val it : string = "c:\\games\\"
> "test".[0..2];; // sous-chaine
val it : string = "tes"
> "test".[2..3];;
val it : string = "st"
> "test".[2..2];;
val it : string = "s"I-6. Les Listes▲
Les listes sont toujours paramétrées par un type : int list correspond au type liste d'entiers. Il peut aussi être noté list<int>. Niveau implémentation, le type liste correspond à une liste chainée.
> [1; 4; 6; 10; 5];; // liste litérale
val it : int list = [1; 4; 6; 10; 5]
> ["this"; "is"; "a"; "test"];;
val it : string list = ["this"; "is"; "a"; "test"]
> 2 :: [3; 4];; // :: est l'opérateur de construction de listes
val it : int list = [2; 3; 4]
> 1 :: 4 :: [5];; // l'opérateur :: est associatif à droite
val it : int list = [1; 4; 5]
> 1 :: 4 :: 5 :: [];;
val it : int list = [1; 4; 5]
> [4; 5] @ [10; 4];; // @ est l'opérateur de concaténation de listes
val it : int list = [4; 5; 10; 4]
> [1..10];; // range comprehension
val it : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]
> [3..2..10];; // pareil, avec incrément
val it : int list = [3; 5; 7; 9]
> [10 .. -1 .. 5];;
val it : int list = [10; 9; 8; 7; 6; 5]I-7. Les Tableaux▲
De la même façon que les listes, ils sont paramétrés par un type. Les éléments d'un tableau sont stockés ensemble dans la mémoire, ce qui permet un accès direct.
> [| 3; 6; 10; 5 |];;
val it : int array = [|3; 6; 10; 5|]
> [| 4; 10; 5 |].[0];;
val it : int = 4
> [| 4; 10; 5 |].[1];;
val it : int = 10
> [| 4; 10; 5 |].[0..1];; // sous-tableau
val it : int array = [|4; 10|]
> [| 'a' .. 'e' |];;
val it : char array = [|'a'; 'b'; 'c'; 'd'; 'e'|]
> [| 8 .. 12 |].[2];;
val it : int = 10
> [| 80 .. 3 .. 100 |].[2..5];;
val it : int array = [|86; 89; 92; 95|]I-8. Les Tuples▲
Un tuple permet de regrouper plusieurs valeurs. Ça peut être vu comme une structure anonyme, dont les champs sont anonymes. Voici quelques exemples, regardez bien le type des valeurs :
> 2, 3;;
val it : int * int = (2, 3)
> "test", 4;;
val it : string * int = ("test", 4)
> 2, 4, 10;;
val it : int * int * int = (2, 4, 10)
> [2; 3], 4, "test";;
val it : int list * int * string = ([2; 3], 4, "test")Pour ce dernier exemple, le type indique que c'est un tuple composé d'une liste d'entiers, d'un entier et d'une chaine de caractères.
> [1, "this"; 2, "is"; 3, "a"; 4, "test"];;
val it : (int * string) list = [(1, "this"); (2, "is"); (3, "a"); (4, "test")]Ceci est une liste de couples.
II. Les Expressions▲
II-1. Du typage fort▲
Les expressions suivantes sont mal typées et génèrent une erreur dès la compilation.
4 + 4.2 // on n'ajoute pas deux valeurs de types différents
'a' + 1 // même remarque. Et il n'y a jamais de cast implicite
[2; 4; "test"] // les éléments d'une liste doivent tous avoir le même type
[| 4; true |] // pareil pour les tableaux
4 = 4. // on ne compare pas deux types différents
(4, 3) = (4, 5, 2) // les tuples n'ont pas le même type.
(3, 'a') = ('a', 3) // même problème : l'ordre dans un tuple est important.Pour convertir un entier en flottant, on utilise la fonction float. Pour la conversion inverse, c'est int (tronque la partie décimale).
II-2. Les Expressions conditionnelles▲
Les expressions conditionnelles ont la syntaxe suivante :
if <expr1> then <expr2> else <expr3>
expr1, expr2 et expr3 sont des expressions quelconques. Il faut toutefois que expr1 s'évalue en type bool,
et que expr2 et expr3 aient le même type t. Cette expression renverra soit l'évaluation de expr2 (si expr1 vaut true),
soit l'évaluation de expr3 (si expr1 vaut false).
Ainsi, le if then else est l'équivalent de l'opérateur ternaire du C. Il renvoie toujours une valeur.
Et il n'y a pas d'équivalent au if du C.
> if true then 4 else 5;;
val it : int = 4
> if 4 > 5 then "test" else "foo";;
val it : string = "foo"
> "abcdefgh".[if 3 * 3 < 5 then 0 else 2];;
val it : char = 'c'
Partout où l'on peut mettre une expression, on peut mettre une condition.
Les deux expressions suivantes sont mal typées :
if 4 then 1 else 2 // 4 n'est pas un booléen
if true then 4.5 else 2 // 4.5 et 2 n'ont pas le même typeIII. Les Déclarations▲
III-1. Les Déclarations locales▲
Pour associer un nom à une valeur, on utilise la construction let..in. Elle a cette syntaxe :
let <ident> = <expr1> in <expr2>
expr1 et expr2 sont deux expressions quelconques, de n'importe quels types. Pour l'identifiant, il faut suivre (en gros),
cette expression rationnelle : [a-zA-Z_][a-zA-Z_0-9']*
Ainsi, les identifiants suivants sont valides :
- toto
- Test
- g'
- _foo_bar42
Dans expr2, on peut utiliser l'identifiant. Il aura la même valeur que exp1. Voici quelques exemples de définitions locales :
> let x = 6 in 6 * 6;;
val it : int = 36
> let x = "a" in x + x + x;;
val it : string = "aaa"Le bloc "let in" étant lui-même une expression, il est possible de les imbriquer.
> let x = 5 in let y = x + 1 in x + y;;
val it : int = 11Partout, absolument partout, où l'on attend une expression, il est possible de définir localement une valeur. Par exemple :
> [| 1 .. let x = 3 in x * x |];;
val it : int array = [|1; 2; 3; 4; 5; 6; 7; 8; 9|]
> "test".[let x = 3 in x - 2];;
val it : char = 'e'Avec la construction let in, on peut masquer une définition déjà existante. Ainsi, si on écrit let x = x + 1, le x de l'expression fait référence à un x déjà défini auparavant. Par exemple :
> let x = 2 in let x = x + 1 in x;;
val it : int = 3Bien sûr, ce genre de construction est à éviter, mais c'est pour montrer comment est gérée la portée.
III-2. Les Déclarations globales▲
On souhaite parfois définir une valeur de façon globale. On utilise pour cela la construction
let <ident> = <expression>.
L'identifiant est alors visible dans la suite du programme.
Ainsi :
> let x = 4;;
val x : int
> x;;
val it : int = 4
> x + 1;;
val it : int = 5Il est important de noter qu'une définition globale n'est pas une expression. On ne peut donc pas l'utiliser là où une expression est attendue.
> let a =
let x = 5 in
let y = 6 in
x * y;;
val a : int
> a;;
val it : int = 30IV. Mode #light▲
Pour activer le mode light, il suffit de taper #light dans le mode interactif ou de mettre cette commande
au début du fichier. Quand il est activé, la grammaire est légèrement modifiée : elle est allégée et se base
sur l'indentation pour désambiguiser la syntaxe. Cela impose une certaine rigueur
(même si les règles d'indentation sont relativement souples), mais permet d'avoir du code bien plus court et lisible.
Ce mode light n'interdit pas d'utiliser le "in" si on le souhaite (ce qui est
pratique lorsque l'on veut mettre une expression sur une ligne), ni l'utilisation
de blocs explicites.
Les spécifications formelles de la syntaxe sont un peu complexes, mais elles sont plutôt intuitives. Par la suite,
j'utiliserai toujours (sauf mention contraire) cette syntaxe light. Et je vous conseille
fortement de l'utiliser systématiquement, d'autant plus qu'il sera mis par défaut
dans une prochaine version.
Le dernier exemple donné devient :
> let a =
let x = 5
let y = 6
x * y;;
val a : int
Comme vous pouvez le voir, tous les "in" peuvent être omis. Nous verrons les autres différences plus tard.
Vous pouvez vous rendre compte que le code n'est pas ambigü : la valeur de a étant forcément une expression,
les définitions de x et de y ne peuvent être que locales.
D'une manière générale, deux valeurs qui ont la même portée ont la même indentation. Pour l'indentation,
vous pouvez utiliser le nombre d'espaces que vous souhaitez, mais soyez consistants. Et surtout, n'utilisez jamais de tabulation
(configurez votre éditeur si nécessaire). Voici un exemple complet, qui compile, et qui reprend la plupart des choses vues
jusqu'à maintenant (regardez bien l'indentation) :
#light
let a =
let x = 10
let y = 15
x * y
let b = a % 5
let c =
if b < 3 then
"oui"
else
"non"
let d = c.[0..1]
printfn "a = %d, b = %d, c = %s, d = %s" a b c d$ fsc test.fs
$ ./test.exe
a = 150, b = 0, c = oui, d = ou
$


