Partilhar via


Parâmetros e argumentos

Este tópico descreve o suporte à linguagem para definir parâmetros e passar argumentos para funções, métodos e propriedades. Inclui informações sobre como passar por referência e como definir e usar métodos que podem ter um número variável de argumentos.

Parâmetros e argumentos

O parâmetro term é usado para descrever os nomes dos valores que se espera que sejam fornecidos. O termo argumento é usado para os valores fornecidos para cada parâmetro.

Os parâmetros podem ser especificados na forma de tupla ou curried, ou em alguma combinação dos dois. Você pode passar argumentos usando um nome de parâmetro explícito. Os parâmetros dos métodos podem ser especificados como opcionais e receber um valor padrão.

Padrões de parâmetros

Os parâmetros fornecidos às funções e métodos são, em geral, padrões separados por espaços. Isso significa que, em princípio, qualquer um dos padrões descritos em Match Expressions pode ser usado em uma lista de parâmetros para uma função ou membro.

Os métodos geralmente usam a forma tupla de passar argumentos. Isso alcança um resultado mais claro da perspetiva de outras linguagens .NET porque o formulário de tupla corresponde à maneira como os argumentos são passados nos métodos .NET.

O formulário curried é mais frequentemente usado com funções criadas usando let associações.

O pseudocódigo a seguir mostra exemplos de argumentos de tupla e curried.

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

As formas combinadas são possíveis quando alguns argumentos estão em tuplas e outros não.

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

Outros padrões também podem ser usados em listas de parâmetros, mas se o padrão de parâmetro não corresponder a todas as entradas possíveis, pode haver uma correspondência incompleta em tempo de execução. A exceção MatchFailureException é gerada quando o valor de um argumento não corresponde aos padrões especificados na lista de parâmetros. O compilador emite um aviso quando um padrão de parâmetro permite correspondências incompletas. Pelo menos um outro padrão é comumente útil para listas de parâmetros, que é o padrão curinga. Você usa o padrão curinga em uma lista de parâmetros quando simplesmente deseja ignorar quaisquer argumentos fornecidos. O código a seguir ilustra o uso do padrão curinga em uma lista de argumentos.

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

O padrão curinga pode ser útil sempre que você não precisar dos argumentos passados, como no ponto de entrada principal para um programa, quando você não estiver interessado nos argumentos de linha de comando que normalmente são fornecidos como uma matriz de cadeia de caracteres, como no código a seguir.

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

Outros padrões que às vezes são usados em argumentos são o padrão e os as padrões de identificador associados a uniões discriminadas e padrões ativos. Você pode usar o padrão de união discriminada de caso único da seguinte maneira.

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

A saída é a seguinte.

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

Padrões ativos podem ser úteis como parâmetros, por exemplo, ao transformar um argumento em um formato desejado, como no exemplo a seguir:

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

Você pode usar o as padrão para armazenar um valor correspondente como um valor local, conforme mostrado na linha de código a seguir.

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

Outro padrão que é usado ocasionalmente é uma função que deixa o último argumento sem nome, fornecendo, como o corpo da função, uma expressão lambda que imediatamente executa uma correspondência de padrão no argumento implícito. Um exemplo disso é a seguinte linha de código.

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

Esse código define uma função que usa uma lista genérica e retorna true se a lista estiver vazia e false caso contrário. O uso de tais técnicas pode tornar o código mais difícil de ler.

Ocasionalmente, padrões que envolvem correspondências incompletas são úteis, por exemplo, se você sabe que as listas em seu programa têm apenas três elementos, você pode usar um padrão como o seguinte em uma lista de parâmetros.

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

O uso de padrões com correspondências incompletas é melhor reservado para prototipagem rápida e outros usos temporários. O compilador emitirá um aviso para esse código. Esses padrões não podem cobrir o caso geral de todas as entradas possíveis e, portanto, não são adequados para APIs de componentes.

Argumentos nomeados

Os argumentos para métodos podem ser especificados por posição em uma lista de argumentos separados por vírgulas ou podem ser passados para um método explicitamente fornecendo o nome, seguido por um sinal de igual e o valor a ser passado. Se especificado fornecendo o nome, eles podem aparecer em uma ordem diferente da usada na declaração.

Os argumentos nomeados podem tornar o código mais legível e mais adaptável a certos tipos de alterações na API, como uma reordenação dos parâmetros do método.

Os argumentos nomeados são permitidos apenas para métodos, não para letfunções vinculadas, valores de função ou expressões lambda.

O exemplo de código a seguir demonstra o uso de argumentos nomeados.

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)

Em uma chamada para um construtor de classe, você pode definir os valores das propriedades da classe usando uma sintaxe semelhante à dos argumentos nomeados. O exemplo a seguir mostra essa sintaxe.

 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)

Para obter mais informações, consulte Construtores (F#).

A mesma técnica, destinada a chamar setters de propriedade, também se aplica a qualquer método de retorno de objeto (como métodos de fábrica):

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

Observe que esses membros podem executar qualquer trabalho arbitrário, a sintaxe é efetivamente uma abreviação para chamar setters de propriedade antes de retornar o valor final.

Parâmetros Opcionais

Você pode especificar um parâmetro opcional para um método usando um ponto de interrogação na frente do nome do parâmetro. Da perspetiva do destinatário, os parâmetros opcionais são interpretados como o tipo de opção F#, para que você possa consultá-los da maneira regular que os tipos de opção são consultados, usando uma match expressão com Some e None. Parâmetros opcionais são permitidos apenas em membros, não em funções criadas usando let associações.

Você pode passar valores opcionais existentes para o método por nome de parâmetro, como ?arg=None ou ?arg=arg?arg=Some(3) . Isso pode ser útil ao criar um método que passa argumentos opcionais para outro método.

Você também pode usar uma função defaultArg, que define um valor padrão de um argumento opcional. A defaultArg função usa o parâmetro opcional como o primeiro argumento e o valor padrão como o segundo.

O exemplo a seguir ilustra o uso de parâmetros opcionais.

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)

A saída é a seguinte.

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

Para fins de interoperabilidade em C# e Visual Basic, você pode usar os atributos [<Optional; DefaultParameterValue<(...)>] em F#, para que os chamadores vejam um argumento como opcional. Isso equivale a definir o argumento como opcional em C# como em MyMethod(int i = 3).

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

Você também pode especificar um novo objeto como um valor de parâmetro padrão. Por exemplo, o Foo membro pode ter um opcional CancellationToken como entrada:

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

O valor dado como argumento para deve corresponder ao DefaultParameterValue tipo do parâmetro. Por exemplo, o seguinte não é permitido:

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

Nesse caso, o compilador gera um aviso e ignorará ambos os atributos completamente. Observe que o valor null padrão precisa ser anotado por tipo, caso contrário, o compilador infere o tipo errado, ou seja, [<Optional; DefaultParameterValue(null:obj)>] o:obj.

Passando por Referência

Passar um valor F# por referência envolve byrefs, que são tipos de ponteiro gerenciados. As orientações para o tipo a utilizar são as seguintes:

  • Use inref<'T> se você só precisa ler o ponteiro.
  • Use outref<'T> se você só precisa escrever no ponteiro.
  • Use byref<'T> se precisar ler e gravar no ponteiro.
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

Como o parâmetro é um ponteiro e o valor é mutável, todas as alterações no valor são retidas após a execução da função.

Você pode usar uma tupla como um valor de retorno para armazenar quaisquer out parâmetros em métodos de biblioteca .NET. Como alternativa, você pode tratar o out parâmetro como um byref parâmetro. O exemplo de código a seguir ilustra ambos os sentidos.

// 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

Matrizes de parâmetros

Ocasionalmente, é necessário definir uma função que toma um número arbitrário de parâmetros de tipo heterogêneo. Não seria prático criar todos os métodos sobrecarregados possíveis para dar conta de todos os tipos que poderiam ser usados. As implementações .NET fornecem suporte para esses métodos por meio do recurso de matriz de parâmetros. Um método que usa uma matriz de parâmetros em sua assinatura pode ser fornecido com um número arbitrário de parâmetros. Os parâmetros são colocados em uma matriz. O tipo dos elementos da matriz determina os tipos de parâmetros que podem ser passados para a função. Se você definir a matriz de parâmetros com System.Object como o tipo de elemento, o código do cliente poderá passar valores de qualquer tipo.

Em F#, matrizes de parâmetros só podem ser definidas em métodos. Eles não podem ser usados em funções autônomas ou funções que são definidas em módulos.

Você define uma matriz de parâmetros usando o ParamArray atributo. O ParamArray atributo só pode ser aplicado ao último parâmetro.

O código a seguir ilustra a chamada de um método .NET que usa uma matriz de parâmetros e a definição de um tipo em F# que tem um método que usa uma matriz de parâmetros.

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

Quando executado em um projeto, a saída do código anterior é a seguinte:

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

Consulte também