Parámetros y argumentos

En este tema se describe la compatibilidad del lenguaje para definir parámetros y pasar 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 parámetro term se usa para describir los nombres de los valores que se espera que se suministre. El argumento de término se usa para los valores proporcionados para cada parámetro.

Los parámetros se pueden especificar en forma de tupla o consultada, 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. Esto consigue un resultado más claro desde la perspectiva de otros lenguajes .NET porque el formulario de tupla coincide con la forma en que se pasan los argumentos en los métodos de .NET.

La forma consultada se suele usar con funciones creadas mediante let enlaces.

El pseudocódigo siguiente muestra ejemplos de tupla y argumentos consultados.

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

Los formularios combinados son posibles 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 haber una coincidencia incompleta en tiempo de ejecución. La MatchFailureException excepción 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ámetro permite coincidencias incompletas. Al menos otro patrón es normalmente útil para las listas de parámetros, y ese es el patrón de caracteres comodín. El patrón comodín se usa en una lista de parámetros cuando simplemente se desea omitir los argumentos proporcionados. El código siguiente muestra el uso del patrón 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 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 y los patrones de identificador asociados a as uniones discriminadas y patrones activos. Puede usar el patrón de unión discriminada de un solo caso como se muestra 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 para almacenar un valor coincidente como un valor local, como as 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 en ocasiones es una función que deja sin nombre el último argumento 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, de lo false contrario, devuelve . 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 solo tienen 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 la creación rápida de prototipos y otros usos temporales. El compilador emitirá una advertencia para dicho código. Estos patrones no pueden cubrir el caso general de todas las entradas posibles y, por tanto, no son adecuados para las API de componentes.

Argumentos con nombre

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

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

Los argumentos con nombre solo se permiten para métodos, no para let funciones enlazadas a , 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 obtener más información, vea Constructores (F#).

Parámetros opcionales

Puede especificar un parámetro opcional para un método mediante un signo de interrogación delante del nombre del parámetro. Los parámetros opcionales se interpretan como el tipo de opción de F#, por lo que puede consultarlos de la manera 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 let enlaces.

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

También puede usar una función defaultArg , que establece un valor predeterminado de un argumento opcional. La defaultArg función 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 los propósitos de la interoperabilidad de C# Visual Basic puede usar los atributos de F#, para que los llamadores vean un argumento [<Optional; DefaultParameterValue<(...)>] 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 de parámetro predeterminado. Por ejemplo, el Foo miembro podría tener un opcional como entrada en su CancellationToken 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, no se permite lo siguiente:

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 debe anotarse como tipo, ya que de lo contrario el compilador deduce el tipo null incorrecto, es decir, [<Optional; DefaultParameterValue(null:obj)>] o:obj .

Pasar por referencia

Pasar un valor de F# por referencia implica byrefs, que son tipos de puntero administrados. La guía para el tipo que 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 si necesita leer y escribir en byref<'T> 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 realizados 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 en métodos out de biblioteca de .NET. Como alternativa, puede tratar el out parámetro como un byref parámetro. En el ejemplo de código siguiente se ilustran ambas maneras.

// 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 toma un número arbitrario de parámetros de tipo heterogéneo. No sería práctico crear todos los métodos sobrecargados posibles para tener en cuenta todos los tipos que se podrían usar. Las implementaciones de .NET proporcionan compatibilidad con estos métodos a través de la característica de matriz de parámetros. Un método que toma una matriz de parámetros en su firma se puede proporcionar con un número arbitrario de parámetros. Los parámetros se ponen en una matriz. El tipo de los elementos de la matriz determina los tipos de parámetro que se pueden pasar a la función. Si define la matriz de parámetros con como tipo de elemento, el código de cliente System.Object 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 independientes o funciones definidas en módulos.

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

El código siguiente muestra tanto la llamada a un método de .NET que toma una matriz de parámetros como 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

Consulta también