Fonctions

Les fonctions sont l’unité fondamentale de l’exécution d’un programme dans tout langage de programmation. Comme dans d’autres langages, une fonction F# a un nom, peut avoir des paramètres et accepter des arguments, et contient un corps. F# prend également en charge des constructions de programmation fonctionnelle, telles que le traitement des fonctions comme valeurs, l’utilisation de fonctions sans nom dans les expressions, la composition de fonctions pour former de nouvelles fonctions, les fonctions curryfiées et la définition implicite de fonctions par l’intermédiaire de l’application partielle d’arguments de fonction.

Pour définir des fonctions, utilisez le mot clé let ou, si la fonction est récursive, la combinaison de mots clés let rec.

Syntaxe

// Non-recursive function definition.
let [inline] function-name parameter-list [ : return-type ] = function-body
// Recursive function definition.
let rec function-name parameter-list = recursive-function-body

Notes

function-name est un identificateur qui représente la fonction. parameter-list se compose de paramètres consécutifs séparés par des espaces. Vous pouvez spécifier un type explicite pour chaque paramètre, comme décrit dans la section Paramètres. Si vous ne spécifiez pas un type d’argument spécifique, le compilateur tente de déduire le type du corps de la fonction. function-body se compose d’une expression. L’expression qui constitue le corps de la fonction est en général une expression composée comprenant plusieurs expressions qui débouchent sur une expression finale, c’est-à-dire la valeur de retour. return-type est un signe deux-points suivi par un type ; son utilisation est facultative. Si vous ne spécifiez pas explicitement le type de la valeur de retour, le compilateur détermine le type de retour à partir de l’expression finale.

Une définition de fonction simple se présente comme suit :

let f x = x + 1

Dans l’exemple précédent, le nom de la fonction est f, l’argument est x, dont le type est int, le corps de la fonction est x + 1, et la valeur de retour est de type int.

Les fonctions peuvent être marquées comme étant inline. Pour plus d’informations sur inline, consultez Fonctions inline.

Étendue

À tout niveau de la portée, sauf dans la portée de module, la réutilisation d’une valeur ou d’un nom de fonction ne constitue pas une erreur. Si vous réutilisez un nom, le nom déclaré en second lieu occulte le nom précédemment déclaré. Toutefois, à la portée de niveau supérieur dans un module, les noms doivent être uniques. Par exemple, le code suivant produit une erreur quand il apparaît au niveau de la portée de module, mais pas quand il apparaît à l’intérieur d’une fonction :

let list1 = [ 1; 2; 3]
// Error: duplicate definition.
let list1 = []
let function1 () =
   let list1 = [1; 2; 3]
   let list1 = []
   list1

Mais le code suivant est acceptable à tout niveau de la portée :

let list1 = [ 1; 2; 3]
let sumPlus x =
// OK: inner list1 hides the outer list1.
   let list1 = [1; 5; 10]
   x + List.sum list1

Paramètres

Les noms des paramètres sont répertoriés après le nom de la fonction. Vous pouvez spécifier un type pour un paramètre, comme indiqué dans l’exemple suivant :

let f (x : int) = x + 1

Si vous spécifiez un type, celui-ci suit le nom du paramètre et est séparé du nom par un signe deux-points. Si vous omettez le type du paramètre, celui-ci est déduit par le compilateur. Par exemple, dans la définition de fonction suivante, le type déduit pour l’argument x est int, car 1 est de type int.

let f x = x + 1

Toutefois, le compilateur tente de rendre la fonction aussi générique que possible. Observez par exemple le code suivant :

let f x = (x, x)

La fonction crée un tuple à partir d’un argument de n’importe quel type. Le type n’étant pas spécifié, la fonction peut être utilisée avec n’importe quel type d’argument. Pour plus d’informations, consultez Généralisation automatique.

Corps de fonction

Un corps de fonction peut contenir des définitions de variables et de fonctions locales. Ces variables et fonctions sont dans la portée du corps de la fonction active, et non à l’extérieur. Vous devez utiliser la mise en retrait pour indiquer qu’une définition se trouve dans un corps de fonction, comme illustré dans l’exemple suivant :

let cylinderVolume radius length =
    // Define a local value pi.
    let pi = 3.14159
    length * pi * radius * radius

Pour plus d’informations, consultez Indications pour la mise en forme du code et Syntaxe détaillée.

Valeurs de retour

Le compilateur utilise l’expression finale dans un corps de fonction pour déterminer la valeur et le type de retour. Il peut déduire le type de l’expression finale à partir d’expressions précédentes. Dans la fonction cylinderVolume présentée dans la section précédente, le type de pi est déterminé à partir du type du littéral 3.14159 comme étant float. Le compilateur utilise le type de pi pour déterminer le type de l’expression length * pi * radius * radius comme étant float. Par conséquent, le type de retour global de la fonction est float.

Pour spécifier explicitement le type de retour, écrivez le code comme suit :

let cylinderVolume radius length : float =
   // Define a local value pi.
   let pi = 3.14159
   length * pi * radius * radius

Selon le code ci-dessus, le compilateur applique float à la fonction entière ; si vous souhaitez l’appliquer également aux types de paramètre, utilisez le code suivant :

let cylinderVolume (radius : float) (length : float) : float

Appel d’une fonction

Pour appeler des fonctions, spécifiez le nom de la fonction, suivi d’un espace et des arguments séparés par des espaces. Par exemple, pour appeler la fonction cylinderVolume et assigner le résultat à la valeur vol, écrivez le code suivant :

let vol = cylinderVolume 2.0 3.0

Application partielle d’arguments

Si vous fournissez un nombre d’arguments inférieur à celui spécifié, vous créez une fonction qui attend les arguments restants. Cette méthode de gestion d’arguments, appelée curryfication, est une caractéristique des langages de programmation fonctionnelle tels que F#. Par exemple, supposons que vous travaillez avec des tuyaux de deux tailles : l’un d’un rayon de 2.0 et l’autre de 3.0. Vous pouvez créer des fonctions qui déterminent le volume des tuyaux, comme suit :

let smallPipeRadius = 2.0
let bigPipeRadius = 3.0

// These define functions that take the length as a remaining
// argument:

let smallPipeVolume = cylinderVolume smallPipeRadius
let bigPipeVolume = cylinderVolume bigPipeRadius

Vous devez ensuite fournir l’argument final selon les besoins pour différentes longueurs de canal des deux tailles différentes :

let length1 = 30.0
let length2 = 40.0
let smallPipeVol1 = smallPipeVolume length1
let smallPipeVol2 = smallPipeVolume length2
let bigPipeVol1 = bigPipeVolume length1
let bigPipeVol2 = bigPipeVolume length2

Fonctions récursives

Les fonctions récursives sont des fonctions qui s’appellent. Elles nécessitent la spécification du mot clé rec après le mot clé let. Appelez la fonction récursive à partir du corps de la fonction, comme vous le feriez pour tout autre appel de fonction. La fonction récursive suivante calcule le nième nombre Fibonacci. La séquence de nombres de Fibonacci est connue depuis l’antiquité ; il s’agit d’une séquence dans laquelle chaque nombre consécutif est la somme des deux nombres précédents dans la séquence.

let rec fib n = if n < 2 then 1 else fib (n - 1) + fib (n - 2)

Certaines fonctions récursives peuvent dépasser la pile du programme ou s’effectuer de manière inefficace si vous ne les écrivez pas avec soin et avec conscience des techniques spéciales, telles que l’utilisation de récursion de queue, d’accumulations et de continuations.

Valeurs de fonction

En F#, toutes les fonctions sont considérées comme des valeurs ; en fait, elles sont appelées valeurs de fonction. Les fonctions étant des valeurs, elles peuvent être utilisées comme arguments d’autres fonctions ou dans d’autres contextes où des valeurs sont utilisées. L’exemple suivant illustre une fonction qui prend une valeur de fonction comme argument :

let apply1 (transform : int -> int ) y = transform y

Vous spécifiez le type d’une valeur de fonction à l’aide du jeton ->. Le type de l’argument se trouve à gauche du jeton, et la valeur de retour à droite. Dans l’exemple précédent, apply1 est une fonction qui prend une fonction transform comme argument, où transform est une fonction qui prend un entier et qui retourne un autre entier. Le code suivant montre comment utiliser apply1 :

let increment x = x + 1

let result1 = apply1 increment 100

La valeur de result est 101 au terme de l’exécution du code précédent.

Plusieurs arguments sont séparés par des jetons -> consécutifs, comme indiqué dans l’exemple suivant :

let apply2 ( f: int -> int -> int) x y = f x y

let mul x y = x * y

let result2 = apply2 mul 10 20

Le résultat est 200.

Expressions lambda

Une expression lambda est une fonction sans nom. Dans les exemples précédents, au lieu de définir des fonctions nommées increment et mul, vous pouvez utiliser des expressions lambda comme suit :

let result3 = apply1 (fun x -> x + 1) 100

let result4 = apply2 (fun x y -> x * y ) 10 20

Pour définir des expressions lambda, utilisez le mot clé fun. Une expression lambda ressemble à une définition de fonction, sauf que le jeton -> est utilisé à la place du jeton = pour séparer la liste d’arguments du corps de la fonction. Comme dans une définition de fonction normale, les types d’argument peuvent être déduits ou spécifiés explicitement, et le type de retour de l’expression lambda est déduit du type de la dernière expression dans le corps. Pour plus d’informations, consultez Expressions lambda : mot clé fun.

Pipelines

L’opérateur |> de canal est utilisé en grande partie lors du traitement des données en F#. Cet opérateur vous permet d’établir des « pipelines » de fonctions de manière flexible. Le pipeline permet aux appels de fonction d’être chaînés en tant qu’opérations successives :

let result = 100 |> function1 |> function2

L’exemple suivant explique comment utiliser ces opérateurs pour créer un pipeline fonctionnel simple :


/// Square the odd values of the input and add one, using F# pipe operators.
let squareAndAddOdd values =
    values
    |> List.filter (fun x -> x % 2 <> 0)
    |> List.map (fun x -> x * x + 1)

let numbers = [ 1; 2; 3; 4; 5 ]

let result = squareAndAddOdd numbers

Le résultat est [2; 10; 26]. L’exemple précédent utilise des fonctions de traitement de liste, illustrant comment les fonctions peuvent être utilisées pour traiter les données lors de la création de pipelines. L’opérateur de pipeline lui-même est défini dans la bibliothèque principale F# comme suit :

let (|>) x f = f x

Composition de fonction

En F#, des fonctions peuvent être composées d’autres fonctions. La composition de deux fonctions function1 et function2 est une autre fonction qui représente l’application de function1 suivie de l’application de function2 :

let function1 x = x + 1
let function2 x = x * 2
let h = function1 >> function2
let result5 = h 100

Le résultat est 202.

L’opérateur de composition prend deux fonctions et retourne une fonction ; par contraste, l’opérateur >>|> de pipeline prend une valeur et une fonction et retourne une valeur. L’exemple de code suivant illustre la différence entre le pipeline et les opérateurs de composition en affichant les différences dans les signatures de la fonction et leur utilisation.

// Function composition and pipeline operators compared.

let addOne x = x + 1
let timesTwo x = 2 * x

// Composition operator
// ( >> ) : ('T1 -> 'T2) -> ('T2 -> 'T3) -> 'T1 -> 'T3
let Compose2 = addOne >> timesTwo

// Backward composition operator
// ( << ) : ('T2 -> 'T3) -> ('T1 -> 'T2) -> 'T1 -> 'T3
let Compose1 = addOne << timesTwo

// Result is 5
let result1 = Compose1 2

// Result is 6
let result2 = Compose2 2

// Pipelining
// Pipeline operator
// ( |> ) : 'T1 -> ('T1 -> 'U) -> 'U
let Pipeline2 x = addOne x |> timesTwo

// Backward pipeline operator
// ( <| ) : ('T -> 'U) -> 'T -> 'U
let Pipeline1 x = addOne <| timesTwo x

// Result is 5
let result3 = Pipeline1 2

// Result is 6
let result4 = Pipeline2 2

Surcharge des fonctions

Vous pouvez surcharger les méthodes d’un type, mais pas les fonctions. Pour plus d’informations, consultez Méthodes.

Voir aussi