La grammaire de F# est relativement simple et cohérente. 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 le 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.

Les entiers

> 42;;
val it : int = 42

Le type de l'expression "42" est donc "int" (entier). Sa valeur est "42".

> 5 + 5;;
val it : int = 10

Le type de l'expression "5 + 5" est "int". Sa valeur est "10".

> 4 * (5 - 3);;
val it : int = 8
> (5 + 6) % 10;;        // % correspond au modulo.
val it : int = 1

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.0

Les caractères

> 'a';;
val it : char = 'a'
> '\n';;                // saut de ligne
val it : char = '\n'
> '\'';;
val it : char = '\''
> ''';;                 // raccourci syntaxique
val it : char = '\''

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 = true

Les chaines de caractères

> "test";;
val it : string = "test"
> "Hello " + "world!";;           // concaténation
val it : string = "Hello world!"
> "test".[0];;                    // Accès à un caractère (0 est le premier caractère)
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"

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 simplement chainée. Il est très rapide d'ajouter un élément au début, mais il est coûteux d'en ajouter un à la fin.

> [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];;          // en utilisant un incrément
val it : int list = [3; 5; 7; 9]
> [10 .. -1 .. 5];;
val it : int list = [10; 9; 8; 7; 6; 5]

Les tableaux

De la même façon que les listes, les tableaux 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 .. 1 .. 100 |].[2..5];;
val it : int array = [|82; 83; 84; 85|]

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 cractè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.

Certaines personnes préfèrent entourer les tuples de parenthèses pour des raisons de lisibilité, mais ce n'est pas obligatoire.

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 conversion 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. est un nombre flottant
 
(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). L'opérateur = teste l'égalité entre deux valeurs en profondeur. Il est donc tout à fait adapté pour comparer des chaînes de caractères, des listes, des tableaux...