Recherche…


Fonctions de plusieurs paramètres

En F #, toutes les fonctions prennent exactement un paramètre . Cela semble une déclaration étrange, car il est trivialement facile de déclarer plus d'un paramètre dans une déclaration de fonction:

let add x y = x + y

Mais si vous tapez cette déclaration de fonction dans l'interpréteur interactif F #, vous verrez que sa signature de type est la suivante:

val add : x:int -> y:int -> int

Sans les noms de paramètres, cette signature est int -> int -> int . L'opérateur -> est associatif à droite, ce qui signifie que cette signature est équivalente à int -> (int -> int) . En d'autres termes, add est une fonction qui prend un paramètre int et renvoie une fonction qui prend un int et renvoie int . Essayez-le:

let addTwo = add 2
// val addTwo : (int -> int)
let five = addTwo 3
// val five : int = 5

Cependant, vous pouvez également appeler une fonction comme add d'une manière plus "conventionnelle", en lui passant directement deux paramètres, et cela fonctionnera comme prévu:

let three = add 1 2
// val three : int = 3

Cela s'applique aux fonctions avec autant de paramètres que vous le souhaitez:

let addFiveNumbers a b c d e = a + b + c + d + e
// val addFiveNumbers : a:int -> b:int -> c:int -> d:int -> e:int -> int
let addFourNumbers = addFiveNumbers 0
// val addFourNumbers : (int -> int -> int -> int -> int)
let addThreeNumbers = addFourNumbers 0
// val addThreeNumbers : (int -> int -> int -> int)
let addTwoNumbers = addThreeNumbers 0
// val addTwoNumbers : (int -> int -> int)
let six = addThreeNumbers 1 2 3  // This will calculate 0 + 0 + 1 + 2 + 3
// val six : int = 6

Cette méthode de réflexion sur les fonctions multi-paramètres est une fonction qui prend un paramètre et retourne de nouvelles fonctions (qui peuvent à leur tour prendre un paramètre et renvoyer de nouvelles fonctions jusqu'à ce que la fonction finale prenne le paramètre final et retourne un résultat) est appelé currying , en l'honneur du mathématicien Haskell Curry, célèbre pour avoir développé le concept. (Il a été inventé par quelqu'un d'autre, mais Curry mérite à juste titre le mérite.)

Ce concept est utilisé dans tout F # et vous voudrez bien le connaître.

Bases des fonctions

La plupart des fonctions de F # sont créées avec la syntaxe let :

let timesTwo x = x * 2

Ceci définit une fonction nommée timesTwo qui prend un seul paramètre x . Si vous exécutez une session F # interactive ( fsharpi sous OS X et Linux, fsi.exe sous Windows) et collez cette fonction dans (et ajoutez le ;; indiquant à fsharpi d'évaluer le code que vous venez de saisir), vous verrez que répond avec:

val timesTwo : x:int -> int

Cela signifie que timesTwo est une fonction qui prend un seul paramètre x de type int et retourne un int . Les signatures de fonctions sont souvent écrites sans les noms de paramètres, vous verrez donc souvent ce type de fonction écrit en tant que int -> int .

Mais attendez! Comment F # a-t-il su que x était un paramètre entier, puisque vous n'avez jamais spécifié son type? Cela est dû à l' inférence de type . Parce que dans le corps de la fonction, vous avez multiplié x par 2 , les types de x et 2 doivent être identiques. (En règle générale, F # ne convertira pas implicitement des valeurs en différents types; vous devez spécifier explicitement toutes les typographies souhaitées).

Si vous voulez créer une fonction qui ne prend pas de paramètre, c'est la mauvaise manière de le faire:

let hello =  // This is a value, not a function
    printfn "Hello world"

La bonne façon de le faire est la suivante:

let hello () =
    printfn "Hello world"

Cette fonction hello a le type unit -> unit , qui est expliqué dans le type "unit" .

Fonctions curry vs tupled

Il existe deux manières de définir des fonctions avec plusieurs paramètres dans F #, fonctions Curry et fonctions Tupled.

let curriedAdd x y = x + y // Signature: x:int -> y:int -> int
let tupledAdd (x, y) = x + y // Signature:  x:int * y:int -> int

Toutes les fonctions définies en dehors de F # (comme le framework .NET) sont utilisées en F # avec la forme Tupled. La plupart des fonctions des modules de base F # sont utilisées avec la forme Curry.

La forme curry est considérée comme idiomatique F #, car elle permet une application partielle. Aucun des deux exemples suivants n'est possible avec la forme Tupled.

let addTen = curriedAdd 10 // Signature: int -> int

// Or with the Piping operator
3 |> curriedAdd 7 // Evaluates to 10

La raison en est que la fonction Curry, lorsqu'elle est appelée avec un paramètre, renvoie une fonction. Bienvenue dans la programmation fonctionnelle !!

let explicitCurriedAdd x = (fun y -> x + y) // Signature: x:int -> y:int -> int
let veryExplicitCurriedAdd = (fun x -> (fun y -> x + y)) // Same signature

Vous pouvez voir que c'est exactement la même signature.

Toutefois, lors de l’interfaçage avec d’autres codes .NET, comme lors de l’écriture de bibliothèques, il est important de définir des fonctions à l’aide de la forme Tupled.

Inlining

Inlining vous permet de remplacer un appel à une fonction par le corps de la fonction.

Cela est parfois utile pour des raisons de performances sur une partie critique du code. Mais la contrepartie est que votre assemblage prendra beaucoup de place puisque le corps de la fonction est dupliqué partout où un appel a eu lieu. Vous devez faire attention lorsque vous décidez d'inclure ou non une fonction.

Une fonction peut être insérée avec le mot-clé inline :

// inline indicate that we want to replace each call to sayHello with the body 
// of the function
let inline sayHello s1 =
    sprintf "Hello %s" s1

// Call to sayHello will be replaced with the body of the function
let s = sayHello "Foo"
// After inlining -> 
// let s = sprintf "Hello %s" "Foo"

printfn "%s" s
// Output
// "Hello Foo"

Un autre exemple avec une valeur locale:

let inline addAndMulti num1 num2 =
    let add = num1 + num2
    add * 2

let i = addAndMulti 2 2
// After inlining ->
// let add = 2 + 2
// let i = add * 2

printfn "%i" i
// Output
// 8

Tuyau avant et arrière

Les opérateurs de tuyauterie sont utilisés pour transmettre des paramètres à une fonction de manière simple et élégante. Il permet d'éliminer les valeurs intermédiaires et facilite la lecture des appels de fonctions.

En F #, il y a deux opérateurs de tuyauterie:

  • Transférer ( |> ): Passer des paramètres de gauche à droite

     let print message =
         printf "%s" message
     
     // "Hello World" will be passed as a parameter to the print function
     "Hello World" |> print
    
  • Arrière ( <| ): Passage des paramètres de droite à gauche

     let print message =
         printf "%s" message
     
     // "Hello World" will be passed as a parameter to the print function
     print <| "Hello World"
    

Voici un exemple sans opérateur de pipe:

// We want to create a sequence from 0 to 10 then:
// 1 Keep only even numbers
// 2 Multiply them by 2
// 3 Print the number

let mySeq = seq { 0..10 }
let filteredSeq = Seq.filter (fun c -> (c % 2) = 0) mySeq
let mappedSeq = Seq.map ((*) 2) filteredSeq
let finalSeq = Seq.map (sprintf "The value is %i.") mappedSeq

printfn "%A" finalSeq

Nous pouvons raccourcir l'exemple précédent et le rendre plus propre avec l'opérateur du tube avant:

// Using forward pipe, we can eliminate intermediate let binding
let finalSeq = 
    seq { 0..10 }
    |> Seq.filter (fun c -> (c % 2) = 0)
    |> Seq.map ((*) 2)
    |> Seq.map (sprintf "The value is %i.")

printfn "%A" finalSeq

Chaque résultat de fonction est passé en paramètre à la fonction suivante.

Si vous souhaitez transmettre plusieurs paramètres à l'opérateur du canal, vous devez ajouter un | pour chaque paramètre supplémentaire et créer un tuple avec les paramètres. L'opérateur de canal natif F # prend en charge jusqu'à trois paramètres (|||> ou <|||).

let printPerson name age =
    printf "My name is %s, I'm %i years old" name age

("Foo", 20) ||> printPerson


Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow