Premiers pas en OCaml/Fonctions
Ce chapitre est le plus important de la leçon. N'hésitez pas à le relire plusieurs fois et effectuer l'exercice dédié aux fonctions.
Convention de nommage
[modifier | modifier le wikicode]Vous verrez à plusieurs reprises dans les définitions les notations suivantes :
- <mot en français> : À remplacer par :
- <type> : un type d'OCaml tel qu'int, float, bool, char, string, unit, <fun>, ...
- <expression> : une expression tel que x + 1.
- <paramètre> : un nom de paramètre ou une valeur constante tel que x ou 1.
function <paramètre> -> <expression>;;
(* peut donner *)
function x -> x;;
function bob -> true;;
(* ... *)
Les fonctions
[modifier | modifier le wikicode]Qu'est-ce qu'une fonction ?
[modifier | modifier le wikicode]Les fonctions OCaml sont très proches des fonctions mathématiques.
Par exemple
s'écrit en OCaml
function x -> x;;
Les fonctions (ou abstractions) sont définies par la syntaxe suivante :
# function <paramètre> -> <expression>;;
- : <type du paramètre> -> <type de l'expression> = <fun>
Exemple d'utilisation
[modifier | modifier le wikicode]On peut par exemple créer une fonction qui prend un entier en paramètre et renvoie son successeur.
# function x -> x + 1;;
# fun x -> x + 1;;
- : int -> int = <fun>
Voyons maintenant comment l’utiliser.
# (function x -> x + 1) 10;;
- : int = 11
Définition
[modifier | modifier le wikicode]Qu'est-ce qu'une définition ?
[modifier | modifier le wikicode]Redéclarer à chaque fois une fonction devient vite pénible. Pour régler le problème, on utilise une définition qui consiste à donner un nom à une valeur. Il faut utiliser le mot-clef let pour assigner un nom à une expression.
let <identificateur> -> <expression>;;
val <identificateur> : <type de expression> = <valeur de l'expression>
On peut aussi effectuer une définition locale avec le mot-clef in:
Les définitions locales sont utiles pour factoriser des calculs répétitifs.
Exemple d'utilisation des définitions
[modifier | modifier le wikicode]Pour assigner un entier à un nom.
# let nombre = 10;;
val nombre : int = 10
On peut ensuite récupérer sa valeur à tout moment.
# nombre;;
val nombre : int = 10
Exemple d'utilisation des définitions locales
[modifier | modifier le wikicode]Si nous voulons effectuer le calcul suivant : Ici est calculé 2 fois. Nous pouvons résoudre ce problème en utilisant les définitions locales.
# let tmp = 2*3 in tmp * 4 + tmp;;
- : int = 30
(* La définition est temporaire. *)
# tmp;;
Error: Unbound value tmp
Définition et fonctions
[modifier | modifier le wikicode]Nous pouvons procéder de même pour donner un nom à nos fonctions.
# let <nom de la fonction> = function <paramètre> -> <expression>;;
val <nom de la fonction> : <type du paramètre> -> <type de l'expression> = <fun>
Reprenons l'exemple de la fonction successeur en lui donnant un nom
# let successeur = function x -> x + 1;;
val successeur : int -> int = <fun>
Il est important de comprendre ici le résultat de l'interpréteur :
val successeur : int -> int = <fun>
|
Disséquons le élément par élément :
- val successeur : : 'Successeur' est ici le nom de la définition.
- int -> int : La fonction prend un entier en paramètre et donne un entier en résultat.
- = <fun> : La valeur de la définition est une fonction.
Fonctions à plusieurs paramètres
[modifier | modifier le wikicode]Une fonction à plusieurs paramètres peut être vue comme une suite de fonctions à un paramètre.
# let fxy = function x -> function y -> x * y;;
(* = *)
# let fxy = fun x y -> x * y;;
val fxy : int -> int -> int = <fun>
Le mot-clef fun permet de soumettre plusieurs paramètres à une fonction contrairement au mot-clef function.
Il est important de bien comprendre le résultat de la fonction précédente :
[...] int -> int -> int [...]
|
Il faut savoir que pour le type des fonctions le parenthésage à droite est implicite. Pour plus de compréhension, mettons des parenthèses. on obtient (int -> (int -> int)). On a donc une fonction qui prend en paramètre un entier et qui rend une fonction prenant en paramètre un entier et qui rend un entier. Imaginez que l’on passe à la fonction fxy qu'un seul paramètre : (2 -> (int -> int)) on récupère alors une fonction qui prend un entier en paramètre et retourne un entier en paramètre (int -> int).
# fxy 2;;
- : int -> int = <fun>
Maintenant, imaginez que l’on passe à la fonction fxy deux paramètres : (2 -> (5 -> int)) on récupère un entier.
# ((fxy 2) 5);;
# (fxy 2) 5;;
# fxy 2 5;;
- : int = 10
Vous remarquez ici que pour l'appel des fonctions le parenthésage à gauche est implicite. C'est très important si vous prenez l'exemple suivant :
# fxy 2 3 + 5;;
- : int = 11
La fonction rend 11 et non pas 16 (2 * 8) OCaml a parenthésé comme ci-dessous.
# ((fxy 2) 3) + 5;;
- : int = 11
Pire si vous mettiez
# fxy 1 + 2 3;;
pour rechercher le produit 3*3. le parenthésage sera effectué comme ci-dessous.
# (((fxy 1) + )2) 3;;
C'est pour cette raison qu'on a droit a une belle erreur de compilation.
Error: This expression has type int -> int
but an expression was expected of type int
Pour corriger ce problème, il faut mettre des parenthèses sur l'opération.
# fxy (1 + 2) 3;;
- : int = 9
Fonction sans paramètre
[modifier | modifier le wikicode]Imaginons maintenant que nous souhaitons une fonction qui renvoie un résultat mais qui n'a pas besoin de paramètre en entrée. Nous pourrions essayer de ne pas spécifier de paramètre comme ci-dessous.
# function -> 1;;
Error: Syntax error
Mais cela ne fonctionne pas.
L'astuce est d’utiliser le type unité.
# let sans_parametre = function () -> 1;;
- : unit -> int = <fun>
Pour appeler la fonction, il suffit de lui passer en paramètre la seule valeur du type unité : ().
# sans_parametre ();;
- : int = 1
Il y a un raccourci d'écriture pour les fonctions.
# let <nom de la fonction> <paramètre 1> ... <paramètre n> = <expression> ;;
val <nom de la fonction> : <type paramètre 1> -> ... -> <type paramètre n> -> <type de l'expression> = <fun>
# let fxy = function x -> function y -> x * y ;;
(* = *)
# let fxy x y = x * y ;;
val fxy : int -> int -> int = <fun>
C'est plus court mais je vous déconseille de l’utiliser tant que vous ne maîtrisez pas encore les fonctions.
Déterminer le type d'une fonction
[modifier | modifier le wikicode]Vous avez le nom d'une fonction ? Vous souhaitez connaître le type de ses arguments sans chercher dans la documentation ? Il suffit simplement de marquer le nom de la fonction dans l'interpréteur pour récupérer son type.
# fxy ;;
- : int -> int -> int = <fun>
Et cela marche aussi pour les opérateurs qui peuvent s'utiliser comme des fonctions. Il faut alors utiliser des parenthèses () pour qu’ils soient considérés comme des fonctions.
# (+) ;;
- : int -> int -> int = <fun>
Grâce à ceci, on sait que la fonction (+) peut s'utiliser de façon.
- Soit avec deux paramètres et la fonction renvoie alors un entier.
# (+) 1 2 ;;
- : int = 3
- Soit avec un paramètre et la fonction renvoie alors une fonction.
# (+) 1 ;;
- : int -> int = <fun>
Sans vous en rendre compte, vous venez de recoder la fonction successeur.
# let successeur = (+) 1 ;;
val successeur : int -> int = <fun>
# successeur 2 ;;
- : int = 3
Références
[modifier | modifier le wikicode]Toutes ces informations sont disponibles sur la documentation officielle :
- [html] • [licence Copyright] • (en) • lien vers le document • Documentation sur les fonctions
- [html] • [licence Copyright] • (fr) • lien vers le document • FAQ sur les fonctions