Paramètres et arguments

Cette rubrique décrit la prise en charge du langage pour la définition de paramètres et le passage d’arguments à des fonctions, méthodes et propriétés. Elle inclut des informations sur la façon de passer par référence, et comment définir et utiliser des méthodes qui peuvent prendre un nombre variable d’arguments.

Paramètres et arguments

Le paramètre de terme est utilisé pour décrire les noms des valeurs qui sont censées être fournies. L’argument de terme est utilisé pour les valeurs fournies pour chaque paramètre.

Les paramètres peuvent être spécifiés sous forme de tuple ou curried, ou dans une combinaison des deux. Vous pouvez passer des arguments à l’aide d’un nom de paramètre explicite. Les paramètres des méthodes peuvent être spécifiés comme facultatifs et en fonction d’une valeur par défaut.

Modèles de paramètres

Les paramètres fournis aux fonctions et aux méthodes sont, en général, des modèles séparés par des espaces. Cela signifie que, en principe, l’un des modèles décrits dans les Expressions de correspondance peut être utilisé dans une liste de paramètres pour une fonction ou un membre.

Les méthodes utilisent généralement la forme tuple de passage d’arguments. Cela permet d’obtenir un résultat plus clair du point de vue d’autres langages .NET, car le formulaire tuple correspond à la façon dont les arguments sont passés dans les méthodes .NET.

Le formulaire curried est le plus souvent utilisé avec les fonctions créées à l’aide de liaisons let.

Le pseudocode suivant montre des exemples d’arguments tuple et curried.

// Tuple form.
member this.SomeMethod(param1, param2) = ...
// Curried form.
let function1 param1 param2 = ...

Les formulaires combinés sont possibles lorsque certains arguments se trouvent dans des tuples, tandis que certains ne le sont pas.

let function2 param1 (param2a, param2b) param3 = ...

D’autres modèles peuvent également être utilisés dans les listes de paramètres, mais si le modèle de paramètre ne correspond pas à toutes les entrées possibles, il peut y avoir une correspondance incomplète au moment de l’exécution. L’exception MatchFailureException est générée lorsque la valeur d’un argument ne correspond pas aux modèles spécifiés dans la liste de paramètres. Le compilateur émet un avertissement lorsqu’un modèle de paramètre autorise des correspondances incomplètes. Au moins un autre modèle est couramment utile pour les listes de paramètres, et c’est-à-dire le modèle générique. Vous utilisez le modèle générique dans une liste de paramètres lorsque vous souhaitez simplement ignorer les arguments fournis. Le code suivant illustre l’utilisation du modèle générique dans une liste d’arguments.

let makeList _ = [ for i in 1 .. 100 -> i * i ]
// The arguments 100 and 200 are ignored.
let list1 = makeList 100
let list2 = makeList 200

Le modèle générique peut être utile chaque fois que vous n’avez pas besoin des arguments passés, comme dans le point d’entrée principal d’un programme, lorsque vous n’êtes pas intéressé par les arguments de ligne de commande qui sont normalement fournis en tant que tableau de chaînes, comme dans le code suivant.

[<EntryPoint>]
let main _ =
    printfn "Entry point!"
    0

D’autres modèles qui sont parfois utilisés dans les arguments sont le modèle as et les modèles d’identificateur associés aux unions discriminatoires et aux modèles actifs. Vous pouvez utiliser le modèle d’union discriminatoire à cas unique comme suit.

type Slice = Slice of int * int * string

let GetSubstring1 (Slice(p0, p1, text)) =
    printfn "Data begins at %d and ends at %d in string %s" p0 p1 text
    text[p0..p1]

let substring = GetSubstring1 (Slice(0, 4, "Et tu, Brute?"))
printfn "Substring: %s" substring

La sortie est la suivante.

Data begins at 0 and ends at 4 in string Et tu, Brute?
Et tu

Les modèles actifs peuvent être utiles en tant que paramètres, par exemple, lors de la transformation d’un argument dans un format souhaité, comme dans l’exemple suivant :

type Point = { x : float; y : float }

let (| Polar |) { x = x; y = y} =
    ( sqrt (x*x + y*y), System.Math.Atan (y/ x) )

let radius (Polar(r, _)) = r
let angle (Polar(_, theta)) = theta

Vous pouvez utiliser le modèle as pour stocker une valeur correspondante en tant que valeur locale, comme indiqué dans la ligne de code suivante.

let GetSubstring2 (Slice(p0, p1, text) as s) = s

Un autre modèle utilisé occasionnellement est une fonction qui laisse le dernier argument sans nom en fournissant, comme corps de la fonction, une expression lambda qui effectue immédiatement une correspondance de modèle sur l’argument implicite. Par exemple, il s’agit de la ligne de code suivante.

let isNil = function [] -> true | _::_ -> false

Ce code définit une fonction qui prend une liste générique et retourne true si la liste est vide et false dans le cas contraire. L’utilisation de ces techniques peut rendre le code plus difficile à lire.

Parfois, les modèles qui impliquent des correspondances incomplètes sont utiles, par exemple, si vous savez que les listes de votre programme n’ont que trois éléments, vous pouvez utiliser un modèle comme celui-ci dans une liste de paramètres.

let sum [a; b; c;] = a + b + c

L’utilisation de modèles qui ont des correspondances incomplètes est la meilleure réserve pour le prototypage rapide et d’autres utilisations temporaires. Le compilateur émet un avertissement pour ce code. Ces modèles ne peuvent pas couvrir le cas général de toutes les entrées possibles et ne conviennent donc pas aux API de composant.

Arguments nommés

Les arguments des méthodes peuvent être spécifiés par position dans une liste d’arguments séparés par des virgules, ou ils peuvent être transmis explicitement à une méthode en fournissant le nom, suivi d’un signe égal et de la valeur à passer. S’ils sont spécifiés en fournissant le nom, ils peuvent apparaître dans un ordre différent de celui utilisé dans la déclaration.

Les arguments nommés peuvent rendre le code plus lisible et plus adaptable à certains types de modifications dans l’API, comme une réorganisation des paramètres de méthode.

Les arguments nommés sont autorisés uniquement pour les méthodes, et non pour les fonctions liées à let, les valeurs de fonction ou les expressions lambda.

L’exemple de code suivant illustre l’utilisation d’arguments nommés.

type SpeedingTicket() =
    member this.GetMPHOver(speed: int, limit: int) = speed - limit

let CalculateFine (ticket : SpeedingTicket) =
    let delta = ticket.GetMPHOver(limit = 55, speed = 70)
    if delta < 20 then 50.0 else 100.0

let ticket1 : SpeedingTicket = SpeedingTicket()
printfn "%f" (CalculateFine ticket1)

Dans un appel à un constructeur de classe, vous pouvez définir les valeurs des propriétés de la classe à l’aide d’une syntaxe similaire à celle des arguments nommés. L’exemple suivant illustre cette syntaxe.

 type Account() =
    let mutable balance = 0.0
    let mutable number = 0
    let mutable firstName = ""
    let mutable lastName = ""
    member this.AccountNumber
       with get() = number
       and set(value) = number <- value
    member this.FirstName
       with get() = firstName
       and set(value) = firstName <- value
    member this.LastName
       with get() = lastName
       and set(value) = lastName <- value
    member this.Balance
       with get() = balance
       and set(value) = balance <- value
    member this.Deposit(amount: float) = this.Balance <- this.Balance + amount
    member this.Withdraw(amount: float) = this.Balance <- this.Balance - amount


let account1 = new Account(AccountNumber=8782108,
                           FirstName="Darren", LastName="Parker",
                           Balance=1543.33)

Pour plus d’informations, consultez Constructeurs (F#).

La même technique, destinée à appeler des setters de propriétés, s’applique également à n’importe quelle méthode de retour d’objet (comme les méthodes de fabrique) :

type Widget() =
    member val Width = 1 with get,set
    member val Height = 1 with get,set

type WidgetFactory =
    static member MakeNewWidget() =
         new Widget()
    static member AdjustWidget(w: Widget) =
         w
let w = WidgetFactory.MakeNewWidget(Width=10)
w.Width // = 10
w.Height // = 1
WidgetFactory.AdjustWidget(w, Height=10)
w.Height // = 10

Notez que ces membres peuvent effectuer n’importe quel travail arbitraire, la syntaxe est effectivement un raccourci pour appeler des setters de propriétés avant de retourner la valeur finale.

Paramètres facultatifs

Vous pouvez spécifier un paramètre facultatif pour une méthode à l’aide d’un point d’interrogation devant le nom du paramètre. Les paramètres facultatifs sont interprétés comme le type d’option F#. Vous pouvez donc les interroger de manière régulière, à l’aide d’une expression match avec Some et None. Les paramètres facultatifs sont autorisés uniquement sur les membres, et non sur les fonctions créées à l’aide de liaisons de let.

Vous pouvez passer des valeurs facultatives existantes à la méthode par nom de paramètre, par exemple ?arg=None, ?arg=Some(3) ou ?arg=arg. Cela peut être utile lors de la création d’une méthode qui transmet des arguments facultatifs à une autre méthode.

Vous pouvez également utiliser une fonction defaultArg, qui définit une valeur par défaut d’un argument facultatif. La fonction defaultArg prend le paramètre facultatif comme premier argument et la valeur par défaut comme deuxième.

L’exemple suivant illustre l’utilisation de paramètres facultatifs.

type DuplexType =
    | Full
    | Half

type Connection(?rate0 : int, ?duplex0 : DuplexType, ?parity0 : bool) =
    let duplex = defaultArg duplex0 Full
    let parity = defaultArg parity0 false
    let mutable rate = match rate0 with
                        | Some rate1 -> rate1
                        | None -> match duplex with
                                  | Full -> 9600
                                  | Half -> 4800
    do printfn "Baud Rate: %d Duplex: %A Parity: %b" rate duplex parity

let conn1 = Connection(duplex0 = Full)
let conn2 = Connection(duplex0 = Half)
let conn3 = Connection(300, Half, true)
let conn4 = Connection(?duplex0 = None)
let conn5 = Connection(?duplex0 = Some(Full))

let optionalDuplexValue : option<DuplexType> = Some(Half)
let conn6 = Connection(?duplex0 = optionalDuplexValue)

La sortie est la suivante.

Baud Rate: 9600 Duplex: Full Parity: false
Baud Rate: 4800 Duplex: Half Parity: false
Baud Rate: 300 Duplex: Half Parity: true
Baud Rate: 9600 Duplex: Full Parity: false
Baud Rate: 9600 Duplex: Full Parity: false
Baud Rate: 4800 Duplex: Half Parity: false

À des fins d’interopérabilité C# et Visual Basic, vous pouvez utiliser les attributs [<Optional; DefaultParameterValue<(...)>] en F#, afin que les appelants voient un argument comme facultatif. Cela équivaut à définir l’argument comme facultatif en C# comme dans MyMethod(int i = 3).

open System
open System.Runtime.InteropServices
type C =
    static member Foo([<Optional; DefaultParameterValue("Hello world")>] message) =
        printfn $"{message}"

Vous pouvez également spécifier un nouvel objet comme valeur de paramètre par défaut. Par exemple, le membre Foo peut avoir une entrée facultative CancellationToken en tant qu’entrée à la place :

open System.Threading
open System.Runtime.InteropServices
type C =
    static member Foo([<Optional; DefaultParameterValue(CancellationToken())>] ct: CancellationToken) =
        printfn $"{ct}"

Valeur donnée en tant qu’argument pour que DefaultParameterValue corresponde au type du paramètre. Par exemple, les éléments suivants ne sont pas autorisés :

type C =
    static member Wrong([<Optional; DefaultParameterValue("string")>] i:int) = ()

Dans ce cas, le compilateur génère un avertissement et ignore complètement les deux attributs. Notez que la valeur par défaut null doit être annotée de type, car sinon le compilateur déduit le type incorrect, c’est-à-dire [<Optional; DefaultParameterValue(null:obj)>] o:obj.

Passer par référence

Le passage d’une valeur F# par référence implique des types byref, qui sont des types de pointeurs managés. Conseils pour le type à utiliser est le suivant :

  • Utilisez inref<'T> si vous devez uniquement lire le pointeur.
  • Utilisez outref<'T> si vous n’avez besoin d’écrire que dans le pointeur.
  • Utilisez byref<'T> si vous avez besoin de lire et d’écrire dans le pointeur.
let example1 (x: inref<int>) = printfn $"It's %d{x}"

let example2 (x: outref<int>) = x <- x + 1

let example3 (x: byref<int>) =
    printfn $"It's %d{x}"
    x <- x + 1

let test () =
    // No need to make it mutable, since it's read-only
    let x = 1
    example1 &x

    // Needs to be mutable, since we write to it
    let mutable y = 2
    example2 &y
    example3 &y // Now 'y' is 3

Étant donné que le paramètre est un pointeur et que la valeur est mutable, toutes les modifications apportées à la valeur sont conservées après l’exécution de la fonction.

Vous pouvez utiliser un tuple comme valeur de retour pour stocker tous les paramètres out dans les méthodes de bibliothèque .NET. Vous pouvez également traiter le paramètre out comme un paramètre byref. L'exemple de code suivant illustre les deux façons.

// TryParse has a second parameter that is an out parameter
// of type System.DateTime.
let (b, dt) = System.DateTime.TryParse("12-20-04 12:21:00")

printfn "%b %A" b dt

// The same call, using an address of operator.
let mutable dt2 = System.DateTime.Now
let b2 = System.DateTime.TryParse("12-20-04 12:21:00", &dt2)

printfn "%b %A" b2 dt2

Tableaux de paramètres

Il est parfois nécessaire de définir une fonction qui prend un nombre arbitraire de paramètres de type hétérogène. Il n’est pas pratique de créer toutes les méthodes surchargées possibles pour tenir compte de tous les types qui pourraient être utilisés. Les implémentations .NET prennent en charge ces méthodes via la fonctionnalité de tableau de paramètres. Une méthode qui prend un tableau de paramètres dans sa signature peut être fournie avec un nombre arbitraire de paramètres. Les paramètres sont placés dans un tableau. Le type des éléments de tableau détermine les types de paramètres qui peuvent être passés à la fonction. Si vous définissez le tableau de paramètres avec System.Object comme type d’élément, le code client peut transmettre des valeurs de n’importe quel type.

Dans F#, les tableaux de paramètres ne peuvent être définis que dans les méthodes. Ils ne peuvent pas être utilisés dans les fonctions autonomes ou les fonctions définies dans les modules.

Vous définissez un tableau de paramètres à l’aide de l’attribut ParamArray. L’attribut ParamArray ne peut être appliqué qu’au dernier paramètre.

Le code suivant illustre à la fois l’appel d’une méthode .NET qui prend un tableau de paramètres et la définition d’un type en F# qui a une méthode qui prend un tableau de paramètres.

open System

type X() =
    member this.F([<ParamArray>] args: Object[]) =
        for arg in args do
            printfn "%A" arg

[<EntryPoint>]
let main _ =
    // call a .NET method that takes a parameter array, passing values of various types
    Console.WriteLine("a {0} {1} {2} {3} {4}", 1, 10.0, "Hello world", 1u, true)

    let xobj = new X()
    // call an F# method that takes a parameter array, passing values of various types
    xobj.F("a", 1, 10.0, "Hello world", 1u, true)
    0

Lors de l’exécution dans un projet, la sortie du code précédent est la suivante :

a 1 10 Hello world 1 True
"a"
1
10.0
"Hello world"
1u
true

Voir aussi