Paramètres et arguments

Cet article décrit la prise en charge du langage pour définir des paramètres et passer des arguments à des fonctions, des méthodes et des propriétés. Il 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.

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.

Exiger des arguments nommés

Pour protéger les appels contre les modifications de l’ordre des paramètres générés, utilisez System.Diagnostics.CodeAnalysis.RequireNamedArgumentsAttribute:

Note

Il s’agit d’une fonctionnalité F# 11.2. Dans le sdk .NET 11.0.100, activez <LangVersion>preview</LangVersion>. L’attribut doit exister dans les métadonnées ou la source référencées ; le compilateur ne le synthétise pas.

open System.Diagnostics.CodeAnalysis

type Query =
    [<RequireNamedArguments>]
    static member Execute(numerator: decimal, denominator: decimal) =
        numerator / denominator

let ratio = Query.Execute(numerator = 3m, denominator = 4m)
// 0.75M; Query.Execute(3m, 4m) is rejected.

Query.Execute(3m, 4m) est rejeté. La réorganisation des déclarations conserve la signification de l’appel nommé ; le renommage d’un paramètre l’interrompt.

La règle couvre les méthodes et les constructeurs, et non les propriétés, les indexeurs, les appels délégués ou les fonctions de module. Les arguments facultatifs peuvent rester omis ; passez des tableaux de paramètres par nom plutôt que comme arguments positionnels développés.

Si vos références n’ont pas l’attribut, définissez-le avant le code consommateur. L’exemple utilise Compatibility.fs; omettez cette définition si vos références le fournissent :

namespace System.Diagnostics.CodeAnalysis

open System

[<Sealed; AttributeUsage(AttributeTargets.Method ||| AttributeTargets.Constructor)>]
type RequireNamedArgumentsAttribute() =
    inherit Attribute()

Sélection de surcharge

Pour distinguer une valeur d’une valeur déjà encapsulée, le séparateur d’attache le plus concret compare autrement les surcharges génériques également classées. Par exemple, il préfère un Task<'T> paramètre de constructeur à un paramètre nu 'T :

Note

Le brise-feu le plus concret et OverloadResolutionPriorityAttribute la prise en charge sont des caractéristiques F# 11.2. Dans le sdk .NET 11.0.100, activez <LangVersion>preview</LangVersion>.

open System.Threading.Tasks

let pending = Task.FromResult 42
let wrapped = ValueTask<_>(pending)
let answer: int = wrapped.Result
// ValueTask<int>, not ValueTask<Task<int>>

Le type déduit est ValueTask<int>: le constructeur encapsule la tâche, au lieu de le traiter comme valeur de résultat de type Task<int>.

Cette règle de dernier recours ne trie pas les types non liés ni les candidats SRTP. Pour inspecter la sélection, activez les diagnostics informationnels FS3575 (surcharge préférée) et FS3576 (surcharge générique contournée).

Pour conserver un ancien point d’entrée binaire tout en préférant une API plus riche pour les nouvelles compilations sources, un auteur de bibliothèque peut définir une valeur plus élevée System.Runtime.CompilerServices.OverloadResolutionPriorityAttribute . Ici, les enregistrements de remplacement de la ligne source de l’appelant via l’installation existante CallerLineNumber :

open System.Runtime.CompilerServices

type Check =
    static member That(condition: bool) =
        if condition then "OK" else "Check failed."

    [<OverloadResolutionPriority(1)>]
    static member That(condition: bool, [<CallerLineNumber>] ?line: int) =
        if condition then "OK" else $"Check failed at line {defaultArg line 0}."

let stock = 0
let diagnostic = Check.That(stock > 0)
// The diagnostic includes the caller's source line.

La priorité 1 sélectionne la surcharge prenant en charge les lignes ; sans l’attribut, la surcharge d’un paramètre gagne. Les membres non annotés ont la priorité 0. L’attribut est disponible dans .NET 9 et versions ultérieures.

Les filtres de priorité applicables aux candidats au sein d’un type déclarant avant les règles de surcharge normales ; les priorités égales utilisent ces règles. Il ne classe pas les modules d’extension ni modifie la sélection de propriété/d’indexeur. Avec cette fonctionnalité activée, les remplacements ne peuvent pas porter l’attribut. Seul le compilateur consommateur a besoin que la fonctionnalité soit activée pour appliquer la priorité.

Sélectionner par type de retour

Pour construire une valeur simple dans le conteneur dont un appelant a besoin, l’attribut de FSharp.Core 11 permet au type de AllowOverloadOnReturnType résultat attendu de participer à la sélection de surcharge. Cet attribut ne nécessite pas d’aperçu :

type Inject =
    [<AllowOverloadOnReturnType>]
    static member Pure(value: 'T) : 'T list = [value]

    [<AllowOverloadOnReturnType>]
    static member Pure(value: 'T) : 'T option = Some value

let batch: string list = Inject.Pure "ready"
let optional: string option = Inject.Pure "ready"
// ["ready"] and Some "ready"

Annotez le résultat pour sélectionner une liste ou une option. Sans l’attribut ou un type de résultat connu, l’appel est ambigu. L’ajout de surcharges de retour uniquement peut donc interrompre les appels déduits. Cela diffère de la construction structurelle, qui sélectionne un membre sur le type de sortie.

Paramètres facultatifs

F# prend en charge deux formes distinctes de paramètres facultatifs pour les méthodes, chacune servant des objectifs différents :

Paramètres facultatifs (F# natif)

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. Du point de vue de l’appelé, 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 à l’aide de l’ombre. La fonction defaultArg prend le paramètre facultatif comme premier argument et la valeur par défaut comme deuxième. Contrairement aux extensions de style C#, cette fonction permet à l’auteur de méthode de savoir si l’appelant a passé une valeur ou non.

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

Vous pouvez également spécifier un paramètre facultatif pour être un type d’option valeur en lui appliquant un [<Struct>] attribut.

type T() =
    static member M([<Struct>] ?p : string) =
        match p with
        | ValueSome s -> printfn "%s" s
        | ValueNone -> printfn "None"

Lorsque vous utilisez un paramètre facultatif soutenu par struct, comme indiqué ci-dessus, vous devez utiliser defaultValueArg au lieu de defaultArg définir la valeur par défaut du paramètre.

Paramètres facultatifs (interopérabilité C#)

Pour les besoins de l’interopérabilité C#, vous pouvez utiliser les attributs en F#, afin que les appelants [<Optional; DefaultParameterValue<(...)>] voient un argument comme facultatif. Cela équivaut à définir l’argument comme facultatif en C# comme dans MyMethod(int i = 3). Ce formulaire a été introduit dans F# 4.1 pour faciliter l’interopérabilité avec le code C#.

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 null par défaut doit être annotée de type ; sinon, le compilateur déduit le type incorrect, autrement dit. [<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.

Toutefois, les fonctions comme printfn obtenir un comportement similaire à l’aide de spécificateurs de format et d’inférence de type pour gérer dynamiquement plusieurs arguments. La fonction printfn ne repose pas sur les tableaux de paramètres ; Au lieu de cela, il utilise le mécanisme de mise en forme de type sécurisé de F# pour traiter différents nombres d’arguments tout en garantissant la vérification du type au moment de la compilation.

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