Buscar..
Funciones de más de un parámetro.
En F #, todas las funciones toman exactamente un parámetro . Esto parece una declaración extraña, ya que es trivialmente fácil declarar más de un parámetro en una declaración de función:
let add x y = x + y
Pero si escribe esa declaración de función en el intérprete interactivo F #, verá que su tipo de firma es:
val add : x:int -> y:int -> int
Sin los nombres de los parámetros, esa firma es int -> int -> int
. El operador ->
es asociativo a la derecha, lo que significa que esta firma es equivalente a int -> (int -> int)
. En otras palabras, add
es una función que toma un parámetro int
, y devuelve una función que toma un int
y devuelve int
. Intentalo:
let addTwo = add 2
// val addTwo : (int -> int)
let five = addTwo 3
// val five : int = 5
Sin embargo, también puede llamar a una función como add
de una manera más "convencional", pasándole directamente dos parámetros, y funcionará como cabría esperar:
let three = add 1 2
// val three : int = 3
Esto se aplica a las funciones con tantos parámetros como desee:
let addFiveNumbers a b c d e = a + b + c + d + e
// val addFiveNumbers : a:int -> b:int -> c:int -> d:int -> e:int -> int
let addFourNumbers = addFiveNumbers 0
// val addFourNumbers : (int -> int -> int -> int -> int)
let addThreeNumbers = addFourNumbers 0
// val addThreeNumbers : (int -> int -> int -> int)
let addTwoNumbers = addThreeNumbers 0
// val addTwoNumbers : (int -> int -> int)
let six = addThreeNumbers 1 2 3 // This will calculate 0 + 0 + 1 + 2 + 3
// val six : int = 6
Este método de pensar en las funciones de parámetros múltiples como funciones que toman un parámetro y devuelven funciones nuevas (que a su vez pueden tomar un parámetro y devolver funciones nuevas, hasta que llegue a la función final que toma el parámetro final y finalmente devuelve un resultado) se llama currying , en honor al matemático Haskell Curry, famoso por desarrollar el concepto. (Fue inventado por otra persona, pero Curry merece merecidamente la mayor parte del crédito por ello).
Este concepto se usa en F #, y querrás estar familiarizado con él.
Fundamentos de funciones
La mayoría de las funciones en F # se crean con la sintaxis de let
:
let timesTwo x = x * 2
Esto define una función llamada timesTwo
que toma un solo parámetro x
. Si ejecuta una sesión interactiva de F # ( fsharpi
en OS X y Linux, fsi.exe
en Windows) y pega esa función (y agrega el ;;
eso le dice a fsharpi
que evalúe el código que acaba de escribir), verá que responde con:
val timesTwo : x:int -> int
Esto significa que timesTwo
es una función que toma un solo parámetro x
de tipo int
, y devuelve un int
. Las firmas de funciones a menudo se escriben sin los nombres de los parámetros, por lo que a menudo verá este tipo de función escrito como int -> int
.
¡Pero espera! ¿Cómo supo F # que x
era un parámetro entero, ya que nunca especificaste su tipo? Eso se debe a la inferencia de tipos . Debido a que en el cuerpo de la función, usted multiplicó x
por 2
, los tipos de x
y 2
deben ser iguales. (Como regla general, F # no convertirá implícitamente los valores a diferentes tipos; debe especificar explícitamente las tipografías que desee).
Si desea crear una función que no tome ningún parámetro, esta es la forma incorrecta de hacerlo:
let hello = // This is a value, not a function
printfn "Hello world"
La forma correcta de hacerlo es:
let hello () =
printfn "Hello world"
Esta función de hello
tiene el tipo de unit -> unit
, que se explica en el tipo de "unidad" .
Funciones Curried vs Tupled
Hay dos formas de definir funciones con múltiples parámetros en F #, funciones Curried y funciones Tupled.
let curriedAdd x y = x + y // Signature: x:int -> y:int -> int
let tupledAdd (x, y) = x + y // Signature: x:int * y:int -> int
Todas las funciones definidas desde fuera de F # (como el marco .NET) se utilizan en F # con la forma de Agrupación. La mayoría de las funciones en los módulos de núcleo F # se utilizan con la forma Curried.
La forma al curry se considera F # idiomática, porque permite una aplicación parcial. Ninguno de los dos ejemplos siguientes son posibles con la forma Agrupada.
let addTen = curriedAdd 10 // Signature: int -> int
// Or with the Piping operator
3 |> curriedAdd 7 // Evaluates to 10
La razón detrás de esto es que la función Curried, cuando se llama con un parámetro, devuelve una función. Bienvenido a la programación funcional !!
let explicitCurriedAdd x = (fun y -> x + y) // Signature: x:int -> y:int -> int
let veryExplicitCurriedAdd = (fun x -> (fun y -> x + y)) // Same signature
Puedes ver que es exactamente la misma firma.
Sin embargo, al interactuar con otro código .NET, como cuando se escriben bibliotecas, es importante definir las funciones utilizando el formulario Agrupado.
En línea
Alinear le permite reemplazar una llamada a una función con el cuerpo de la función.
Esto a veces es útil por razones de rendimiento en una parte crítica del código. Pero la contraparte es que su ensamblaje ocupará mucho espacio ya que el cuerpo de la función se duplica en todos los lugares en que se produjo una llamada. Debe tener cuidado al decidir si desea integrar una función o no.
Una función se puede alinear con la palabra clave en inline
:
// inline indicate that we want to replace each call to sayHello with the body
// of the function
let inline sayHello s1 =
sprintf "Hello %s" s1
// Call to sayHello will be replaced with the body of the function
let s = sayHello "Foo"
// After inlining ->
// let s = sprintf "Hello %s" "Foo"
printfn "%s" s
// Output
// "Hello Foo"
Otro ejemplo con valor local:
let inline addAndMulti num1 num2 =
let add = num1 + num2
add * 2
let i = addAndMulti 2 2
// After inlining ->
// let add = 2 + 2
// let i = add * 2
printfn "%i" i
// Output
// 8
Pipa hacia adelante y hacia atrás
Los operadores de tuberías se utilizan para pasar parámetros a una función de una manera simple y elegante. Permite eliminar valores intermedios y hacer que las llamadas a funciones sean más fáciles de leer.
En F #, hay dos operadores de tubería:
Adelante (
|>
): pasando parámetros de izquierda a derechalet print message = printf "%s" message // "Hello World" will be passed as a parameter to the print function "Hello World" |> print
Atrás (
<|
): pasar parámetros de derecha a izquierdalet print message = printf "%s" message // "Hello World" will be passed as a parameter to the print function print <| "Hello World"
Aquí hay un ejemplo sin operadores de tubería:
// We want to create a sequence from 0 to 10 then:
// 1 Keep only even numbers
// 2 Multiply them by 2
// 3 Print the number
let mySeq = seq { 0..10 }
let filteredSeq = Seq.filter (fun c -> (c % 2) = 0) mySeq
let mappedSeq = Seq.map ((*) 2) filteredSeq
let finalSeq = Seq.map (sprintf "The value is %i.") mappedSeq
printfn "%A" finalSeq
Podemos acortar el ejemplo anterior y hacerlo más limpio con el operador de tubería delantera:
// Using forward pipe, we can eliminate intermediate let binding
let finalSeq =
seq { 0..10 }
|> Seq.filter (fun c -> (c % 2) = 0)
|> Seq.map ((*) 2)
|> Seq.map (sprintf "The value is %i.")
printfn "%A" finalSeq
El resultado de cada función se pasa como un parámetro a la siguiente función.
Si desea pasar varios parámetros al operador de tubería, debe agregar un |
para cada parámetro adicional y crear una tupla con los parámetros. El operador de tubería F # nativo admite hasta tres parámetros (|||> o <|||).
let printPerson name age =
printf "My name is %s, I'm %i years old" name age
("Foo", 20) ||> printPerson