Parámetros y argumentos

En este tema se describe la compatibilidad del lenguaje con la definición de parámetros y el paso de argumentos a funciones, métodos y propiedades. Incluye información sobre cómo pasar por referencia y cómo definir y usar métodos que pueden tomar un número variable de argumentos.

Parámetros y argumentos

El término parámetro se usa para describir los nombres de los valores que se espera que se proporcionen. El término argumento se usa para los valores proporcionados para cada parámetro.

Los parámetros se pueden especificar en forma de tupla o currificados, o en alguna combinación de los dos. Puede pasar argumentos mediante un nombre de parámetro explícito. Los parámetros de los métodos se pueden especificar como opcionales y se les puede dar un valor predeterminado.

Patrones de parámetros

Los parámetros proporcionados a funciones y métodos son, en general, patrones separados por espacios. Esto significa que, en principio, cualquiera de los patrones descritos en Expresiones de coincidencia se puede usar en una lista de parámetros para una función o miembro.

Los métodos suelen usar la forma de tupla de pasar argumentos. Con ello se consigue un resultado más claro desde la perspectiva de otros lenguajes .NET porque la forma de tupla coincide con el modo en que los argumentos se pasan en los métodos .NET.

La forma currificada se usa más a menudo con las funciones creadas mediante enlaces let.

En el pseudocódigo siguiente se muestran ejemplos de argumentos de tupla y currificados.

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

Son posibles formas combinadas cuando algunos argumentos están en tuplas y otros no.

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

También se pueden usar otros patrones en listas de parámetros, pero si el patrón de parámetros no coincide con todas las entradas posibles, puede que haya una coincidencia incompleta en tiempo de ejecución. La excepción MatchFailureException se genera cuando el valor de un argumento no coincide con los patrones especificados en la lista de parámetros. El compilador emite una advertencia cuando un patrón de parámetros permite coincidencias incompletas. Suele ser de utilidad al menos otro patrón para las listas de parámetros y ese es el patrón de caracteres comodín. Use el patrón de caracteres comodín en una lista de parámetros cuando simplemente quiera omitir los argumentos proporcionados. En el código siguiente se muestra el uso del patrón de caracteres comodín en una 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

El patrón de caracteres comodín puede ser útil siempre que no necesite los argumentos pasados, como en el punto de entrada principal a un programa, cuando no esté interesado en los argumentos de la línea de comandos que normalmente se proporcionan como una matriz de cadenas, como en el código siguiente.

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

Otros patrones que a veces se usan en argumentos son el patrón as y los patrones de identificador asociados a uniones discriminadas y patrones activos. Puede usar el patrón de unión discriminado de un solo caso como se indica a continuación.

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 salida es la siguiente.

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

Los patrones activos pueden ser útiles como parámetros, por ejemplo, al transformar un argumento en un formato deseado, como en el ejemplo siguiente:

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

Puede usar el patrón as para almacenar un valor coincidente como un valor local, como se muestra en la siguiente línea de código.

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

Otro patrón que se usa ocasionalmente es una función que deja el último argumento sin nombre proporcionando, como cuerpo de la función, una expresión lambda que realiza inmediatamente una coincidencia de patrón en el argumento implícito. Un ejemplo de esto es la siguiente línea de código.

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

Este código define una función que toma una lista genérica y devuelve true si la lista está vacía y false si no. El uso de estas técnicas puede dificultar la lectura del código.

En ocasiones, los patrones que implican coincidencias incompletas son útiles; por ejemplo, si sabe que las listas del programa tienen solo tres elementos, puede usar un patrón como el siguiente en una lista de parámetros.

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

El uso de patrones que tienen coincidencias incompletas se reserva mejor para crear prototipos rápidos y otros usos temporales. El compilador emitirá una advertencia para este código. Estos patrones no pueden cubrir el caso general de todas las entradas posibles y, por lo tanto, no son adecuados para las API de componentes.

Argumentos con nombre

Se pueden especificar argumentos para métodos por posición en una lista de argumentos separados por coma, o se pueden pasar explícitamente a un método proporcionando el nombre, seguido de un signo igual y el valor que se va a pasar. Si se especifican proporcionando el nombre, pueden aparecer en un orden diferente del usado en la declaración.

Los argumentos con nombre pueden hacer que el código sea más legible y adaptable a determinados tipos de cambios en la API, como una reordenación de los parámetros de método.

Los argumentos con nombre solo se permiten para métodos, no para funciones enlazadas a let, valores de función o expresiones lambda.

En el ejemplo de código siguiente se muestra el uso de argumentos con nombre.

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)

En una llamada a un constructor de clase, puede establecer los valores de las propiedades de la clase mediante una sintaxis similar a la de los argumentos con nombre. En el ejemplo siguiente se muestra esta sintaxis:

 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 más información, consulte Constructores (F#).

La misma técnica, diseñada para llamar a establecedores de propiedades, también se aplica a cualquier método que devuelva objetos (como los 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

Tenga en cuenta que esos miembros podrían realizar cualquier trabajo arbitrario, la sintaxis es efectivamente una forma abreviada de llamar a establecedores de propiedades antes de devolver el valor final.

Parámetros opcionales

Puede especificar un parámetro opcional para un método colocando un signo de interrogación delante del nombre del parámetro. Los parámetros opcionales se interpretan como el tipo de opción F#, por lo que puede consultarlos de la forma habitual en que se consultan los tipos de opción mediante una expresión match con Some y None. Los parámetros opcionales solo se permiten en los miembros, no en las funciones creadas mediante enlaces let.

Puede pasar valores opcionales existentes al método por nombre de parámetro, como ?arg=None, ?arg=Some(3) o ?arg=arg. Esto puede ser útil al crear un método que pase argumentos opcionales a otro método.

También puede usar una función defaultArg, que establece un valor predeterminado de un argumento opcional. La función defaultArg toma el parámetro opcional como primer argumento y el valor predeterminado como segundo.

En el ejemplo siguiente se muestra el uso de parámetros opcionales.

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 salida es la siguiente.

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 la interoperabilidad de C# y Visual Basic, puede usar los atributos [<Optional; DefaultParameterValue<(...)>] en F#, de modo que los autores de llamadas vean un argumento como opcional. Esto equivale a definir el argumento como opcional en C# como en MyMethod(int i = 3).

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

También puede especificar un nuevo objeto como valor predeterminado del parámetro. Por ejemplo, el miembro Foo podría tener un valor CancellationToken opcional como entrada en su lugar:

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

El valor especificado como argumento para DefaultParameterValue debe coincidir con el tipo del parámetro. Por ejemplo, la siguiente vista no se admite:

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

En este caso, el compilador genera una advertencia y omitirá ambos atributos por completo. Tenga en cuenta que el valor predeterminado null debe anotarse por tipo, ya que, de lo contrario, el compilador deduce el tipo incorrecto, es decir [<Optional; DefaultParameterValue(null:obj)>] o:obj.

Paso por referencia

Pasar un valor de F# por referencia implica el uso de byrefs, que son tipos de puntero administrados. La guía sobre qué tipo se va a usar es la siguiente:

  • Use inref<'T> si solo necesita leer el puntero.
  • Use outref<'T> si solo necesita escribir en el puntero.
  • Use byref<'T> si necesita leer y escribir en el puntero.
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

Dado que el parámetro es un puntero y el valor es mutable, los cambios en el valor se conservan después de la ejecución de la función.

Puede usar una tupla como valor devuelto para almacenar los parámetros out en los métodos de biblioteca de .NET. Como alternativa, puede tratar el parámetro out como parámetro byref. En el siguiente ejemplo de código se muestran ambas formas.

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

Matrices de parámetros

En ocasiones, es necesario definir una función que tome un número arbitrario de parámetros de tipo heterogéneo. No sería práctico crear todos los posibles métodos sobrecargados para dar cuenta de todos los tipos que se podrían usar. Las implementaciones de .NET proporcionan compatibilidad con estos métodos mediante la característica de matriz de parámetros. A un método que toma una matriz de parámetros en su firma se le puede proporcionar un número arbitrario de parámetros. Los parámetros se colocan en una matriz. El tipo de los elementos de matriz determina los tipos de parámetro que se pueden pasar a la función. Si define la matriz de parámetros con System.Object como tipo de elemento, el código de cliente puede pasar valores de cualquier tipo.

En F#, las matrices de parámetros solo se pueden definir en métodos. No se pueden usar en funciones o funciones independientes definidas en módulos.

Una matriz de parámetros se define mediante el atributo ParamArray. El atributo ParamArray solo se puede aplicar al último parámetro.

En el código siguiente se muestra la llamada a un método .NET que toma una matriz de parámetros y la definición de un tipo en F# que tiene un método que toma una 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

Cuando se ejecuta en un proyecto, la salida del código anterior es la siguiente:

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

Consulte también