Compartir a través de


Funciones

Las funciones son la unidad fundamental de ejecución del programa en cualquier lenguaje de programación. Como en otros lenguajes, una función de F# tiene un nombre, puede tener parámetros y tomar argumentos y tener un cuerpo. F# también admite construcciones de programación funcionales, como tratar funciones como valores, usar funciones sin nombre en expresiones, composición de funciones para formar nuevas funciones, funciones curriadas y la definición implícita de funciones mediante la aplicación parcial de argumentos de función.

Las funciones se definen mediante la let palabra clave o, si la función es recursiva, la combinación de palabra let rec clave.

Sintaxis

// Non-recursive function definition.
let [inline] function-name parameter-list [: return-type ] = function-body
// Recursive function definition.
let rec function-name parameter-list = recursive-function-body

Observaciones

El nombre de la función es un identificador que representa la función. La lista de parámetros consta de parámetros sucesivos separados por espacios. Puede especificar un tipo explícito para cada parámetro, tal como se describe en la sección Parámetros. Si no especifica un tipo de argumento específico, el compilador intenta deducir el tipo del cuerpo de la función. El cuerpo de la función consta de una expresión. La expresión que constituye el cuerpo de la función suele ser una expresión compuesta que consta de una serie de expresiones que culminan en una expresión final que es el valor devuelto. El tipo de valor devuelto es un signo de dos puntos seguido de un tipo y es opcional. Si no especifica explícitamente el tipo del valor devuelto, el compilador determina el tipo de valor devuelto de la expresión final.

Una definición de función simple es similar a la siguiente:

let f x = x + 1

En el ejemplo anterior, el nombre de función es f, el argumento es x, que tiene el tipo int, el cuerpo de la función es x + 1 y el valor devuelto es de tipo int.

Las funciones se pueden marcar como inline. Para obtener información sobre inline, vea Funciones insertadas.

Ámbito

En cualquier nivel de ámbito distinto del ámbito del módulo, no es un error reutilizar un valor o un nombre de función. Si reutiliza un nombre, el nombre declarado más adelante sombrea el nombre declarado anteriormente. Sin embargo, en el ámbito de nivel superior de un módulo, los nombres deben ser únicos. Por ejemplo, el código siguiente genera un error cuando aparece en el ámbito del módulo, pero no cuando aparece dentro de una función:

let list1 = [ 1; 2; 3 ]
// Error: duplicate definition.
let list1 = []

let function1 () =
    let list1 = [ 1; 2; 3 ]
    let list1 = []
    list1

Pero el código siguiente es aceptable en cualquier nivel de ámbito:

let list1 = [ 1; 2; 3 ]

let sumPlus x =
    // OK: inner list1 hides the outer list1.
    let list1 = [ 1; 5; 10 ]
    x + List.sum list1

Parámetros

Los nombres de los parámetros se muestran después del nombre de la función. Puede especificar un tipo para un parámetro, como se muestra en el ejemplo siguiente:

let f (x: int) = x + 1

Si especifica un tipo, sigue el nombre del parámetro y está separado del nombre por dos puntos. Si omite el tipo del parámetro , el compilador deduce el tipo de parámetro. Por ejemplo, en la siguiente definición de función, se deduce que el argumento x es de tipo int porque 1 es de tipo int.

let f x = x + 1

Sin embargo, el compilador intentará hacer que la función sea tan genérica como sea posible. Por ejemplo, tenga en cuenta el código siguiente:

let f x = (x, x)

La función crea una tupla a partir de un argumento de cualquier tipo. Dado que no se especifica el tipo, la función se puede usar con cualquier tipo de argumento. Para obtener más información, consulte Generalización automática.

Cuerpos de función

Un cuerpo de función puede contener definiciones de variables y funciones locales. Estas variables y funciones están en el ámbito del cuerpo de la función actual, pero no fuera de ella. Debe usar sangría para indicar que una definición está en un cuerpo de función, como se muestra en el ejemplo siguiente:

let cylinderVolume radius length =
    // Define a local value pi.
    let pi = 3.14159
    length * pi * radius * radius

Para obtener más información, vea Instrucciones de formato de código y Sintaxis detallada.

Valores devueltos

El compilador usa la expresión final en un cuerpo de función para determinar el valor devuelto y el tipo. El compilador puede deducir el tipo de la expresión final de las expresiones anteriores. En la función cylinderVolume, que se muestra en la sección anterior, el tipo de pi se determina a partir del tipo del literal 3.14159 que se va a ser float. El compilador usa el tipo de pi para determinar el tipo de la expresión length * pi * radius * radius que se va a ser float. Por lo tanto, el tipo de valor devuelto general de la función es float.

Para especificar explícitamente el tipo de valor devuelto, escriba el código de la siguiente manera:

let cylinderVolume radius length : float =
    // Define a local value pi.
    let pi = 3.14159
    length * pi * radius * radius

A medida que se escribe el código anterior, el compilador aplica float a toda la función; Si quiere aplicarlo también a los tipos de parámetros, use el código siguiente:

let cylinderVolume (radius: float) (length: float) : float

Llamar a una función

Para llamar a las funciones, especifique el nombre de la función seguido de un espacio y, a continuación, los argumentos separados por espacios. Por ejemplo, para llamar a la función cylinderVolume y asignar el resultado al valor vol, escriba el código siguiente:

let vol = cylinderVolume 2.0 3.0

Aplicación parcial de argumentos

Si proporciona menos del número especificado de argumentos, cree una nueva función que espere los argumentos restantes. Este método de control de argumentos se conoce como currying y es una característica de lenguajes de programación funcionales como F#. Por ejemplo, supongamos que está trabajando con dos tamaños de canalización: uno tiene un radio de 2,0 y el otro tiene un radio de 3,0. Puede crear funciones que determinen el volumen de canalización de la siguiente manera:

let smallPipeRadius = 2.0
let bigPipeRadius = 3.0

// These define functions that take the length as a remaining
// argument:

let smallPipeVolume = cylinderVolume smallPipeRadius
let bigPipeVolume = cylinderVolume bigPipeRadius

A continuación, proporcionaría el argumento final según sea necesario para varias longitudes de canalización de los dos tamaños diferentes:

let length1 = 30.0
let length2 = 40.0
let smallPipeVol1 = smallPipeVolume length1
let smallPipeVol2 = smallPipeVolume length2
let bigPipeVol1 = bigPipeVolume length1
let bigPipeVol2 = bigPipeVolume length2

Funciones recursivas

Las funciones recursivas son funciones que se llaman a sí mismas. Requieren que especifique la palabra clave rec después de la palabra clave let . Invoque la función recursiva desde el cuerpo de la función tal como invocaría cualquier llamada de función. La siguiente función recursiva calcula el número nº Fibonacci. La secuencia de números de Fibonacci se conoce desde la antigüedad y es una secuencia en la que cada número sucesivo es la suma de los dos números anteriores en la secuencia.

let rec fib n =
    if n < 2 then 1 else fib (n - 1) + fib (n - 2)

Algunas funciones recursivas pueden desbordar la pila del programa o realizar ineficazmente si no las escribe con cuidado y con reconocimiento de técnicas especiales, como el uso de recursión de cola, acumuladores y continuaciones.

Valores de función

En F#, todas las funciones se consideran valores; de hecho, se conocen como valores de función. Dado que las funciones son valores, se pueden usar como argumentos para otras funciones o en otros contextos donde se usan valores. A continuación se muestra un ejemplo de una función que toma un valor de función como argumento:

let apply1 (transform: int -> int) y = transform y

Especifique el tipo de un valor de función mediante el -> token. En el lado izquierdo de este token es el tipo del argumento y, en el lado derecho, es el valor devuelto. En el ejemplo anterior, apply1 es una función que toma una función transform como argumento, donde transform es una función que toma un entero y devuelve otro entero. En el código siguiente se muestra cómo usar apply1:

let increment x = x + 1

let result1 = apply1 increment 100

El valor de será 101 después de result que se ejecute el código anterior.

Varios argumentos están separados por tokens sucesivos -> , como se muestra en el ejemplo siguiente:

let apply2 (f: int -> int -> int) x y = f x y

let mul x y = x * y

let result2 = apply2 mul 10 20

El resultado es 200.

Expresiones lambda

Una expresión lambda es una función sin nombre. En los ejemplos anteriores, en lugar de definir el incremento y lamul de funciones con nombre, podría usar expresiones lambda como se indica a continuación:

let result3 = apply1 (fun x -> x + 1) 100

let result4 = apply2 (fun x y -> x * y) 10 20

Las expresiones lambda se definen mediante la fun palabra clave . Una expresión lambda es similar a una definición de función, excepto que en lugar del = token, el -> token se usa para separar la lista de argumentos del cuerpo de la función. Como en una definición de función regular, los tipos de argumento se pueden inferir o especificar explícitamente, y el tipo de valor devuelto de la expresión lambda se deduce del tipo de la última expresión del cuerpo. Para obtener más información, vea Expresiones lambda: la fun palabra clave .

Tuberías

El operador de canalización |> se usa ampliamente al procesar datos en F#. Este operador te permite crear "tuberías" de funciones de manera flexible. La canalización permite encadenar las llamadas de función como operaciones sucesivas:

let result = 100 |> function1 |> function2

En el ejemplo siguiente se explica cómo puede usar estos operadores para crear una canalización funcional sencilla:

/// Square the odd values of the input and add one, using F# pipe operators.
let squareAndAddOdd values =
    values |> List.filter (fun x -> x % 2 <> 0) |> List.map (fun x -> x * x + 1)

let numbers = [ 1; 2; 3; 4; 5 ]

let result = squareAndAddOdd numbers

El resultado es [2; 10; 26]. En el ejemplo anterior se usan funciones de procesamiento de listas, que muestran cómo se pueden usar las funciones para procesar datos al compilar canalizaciones. El propio operador de canalización se define en la biblioteca principal de F# como se indica a continuación:

let (|>) x f = f x

Composición de funciones

Las funciones de F# se pueden componer desde otras funciones. La composición de dos funciones function1 y function2 es otra función que representa la aplicación de function1 seguida de la aplicación de function2:

let function1 x = x + 1
let function2 x = x * 2
let h = function1 >> function2
let result5 = h 100

El resultado es 202.

El operador >> de composición toma dos funciones y devuelve una función; por el contrario, el operador |> de canalización toma un valor y una función y devuelve un valor. En el ejemplo de código siguiente se muestra la diferencia entre los operadores de canalización y composición mostrando las diferencias en las firmas de función y el uso.

// Function composition and pipeline operators compared.
let addOne x = x + 1
let timesTwo x = 2 * x

// Composition operator
// ( >> ) : ('T1 -> 'T2) -> ('T2 -> 'T3) -> 'T1 -> 'T3
let Compose2 = addOne >> timesTwo

// Backward composition operator
// ( << ) : ('T2 -> 'T3) -> ('T1 -> 'T2) -> 'T1 -> 'T3
let Compose1 = addOne << timesTwo

// Result is 5
let result1 = Compose1 2

// Result is 6
let result2 = Compose2 2

// Pipelining
// Pipeline operator
// ( |> ) : 'T1 -> ('T1 -> 'U) -> 'U
let Pipeline2 x = addOne x |> timesTwo

// Backward pipeline operator
// ( <| ) : ('T -> 'U) -> 'T -> 'U
let Pipeline1 x = addOne <| timesTwo x

// Result is 5
let result3 = Pipeline1 2

// Result is 6
let result4 = Pipeline2 2

Sobrecargar funciones

Puede sobrecargar métodos de un tipo, pero no funciones. Para obtener más información, vea Métodos.

Consulte también