Parâmetros e argumentos

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

Parâmetros e argumentos

O parâmetro de termo é usado para descrever os nomes dos valores que devem ser fornecidos. O termo argumento é usado para os valores fornecidos para cada parâmetro.

Os parâmetros podem ser especificados na forma tupla ou na forma 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âmetro

Os parâmetros fornecidos para funções e métodos são, em geral, padrões separados por espaços. Isso significa que, em princípio, qualquer padrão descrito em Expressões de Correspondência pode ser usado em uma lista de parâmetros para uma função ou membro.

Os métodos geralmente usam a forma de tupla de argumentos de passagem. Isso obtém um resultado mais claro da perspectiva de outras linguagens .NET porque o formulário de tupla corresponde à maneira como os argumentos são passados em métodos .NET.

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

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

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

Formulários combinados 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 no 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 avisa quando um padrão de parâmetro permite correspondências incompletas. Pelo menos um outro padrão é comumente útil para listas de parâmetros e esse é o padrão curinga. Você usa o padrão curinga em uma lista de parâmetros quando simplesmente quer ignorar os 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 quando você não precisar dos argumentos passados, como no ponto de entrada principal de 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 as e os padrões de identificador associados a uniões discriminadas e padrões ativos. Você pode usar o padrão de união discriminada por 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 padrão as 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 usado ocasionalmente é uma função que deixa o último argumento sem nome fornecendo, como o corpo da função, uma expressão lambda que executa imediatamente 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 dessas 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 souber que as listas no 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 abranger o caso geral de todas as entradas possíveis e, portanto, não são adequados para APIs de componente.

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 fornecendo explicitamente o nome, seguidos por um sinal de igual e o valor a ser passado. Se especificado fornecendo o nome, eles podem aparecer em uma ordem diferente daquela usada na declaração.

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

Argumentos nomeados são permitidos apenas para métodos, não para funções limitadas a let, 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 a 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 saber mais, veja Construtores (F#).

A mesma técnica, destinada a chamar setters de propriedades, 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 é, na verdade, um atalho para chamar os definidores 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. Os parâmetros opcionais são interpretados como o tipo de opção F#, para que você possa consultá-los da maneira regular como os tipos de opção são consultados, usando uma expressão match com Some e None. Parâmetros opcionais são permitidos somente em membros, não em funções criadas usando associações let.

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

Você também pode usar uma função defaultArg, que define um valor padrão de um argumento opcional. A função defaultArg 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 do C# e do Visual Basic, você pode usar os atributos [<Optional; DefaultParameterValue<(...)>] no F#, para que os chamadores vejam um argumento como opcional. Isso é equivalente 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 membro Foo pode ter um CancellationToken opcional como entrada:

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

O valor fornecido como argumento para DefaultParameterValue precisa corresponder ao 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 ignora os dois atributos completamente. Observe que o null do valor padrão precisa ser anotado por tipo, caso contrário, o compilador infere o tipo errado, ou seja, [<Optional; DefaultParameterValue(null:obj)>] o:obj.

Passagem por referência

Passar um valor F# por referência envolve byrefs, que são tipos de ponteiro gerenciados. As diretrizes para qual tipo usar são as seguintes:

  • Use inref<'T> se você só precisar ler o ponteiro.
  • Use outref<'T> se você só precisar gravar o 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 mantidas após a execução da função.

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

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

Às vezes, é necessário definir uma função que usa 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 considerar todos os tipos que poderiam ser usados. As implementações do .NET são compatíveis com esses métodos por meio do recurso de matriz de parâmetros. Um método que usa uma matriz de parâmetros na 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 de elementos de matriz determina os tipos de parâmetro 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.

No F#, as matrizes de parâmetros só podem ser definidas em métodos. Elas não podem ser usadas em funções autônomas ou funções definidas em módulos.

Você define uma matriz de parâmetros usando o atributo ParamArray. O atributo ParamArray 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

Confira também