"Int-> int-> int" ¿Qué significa esto en F#?
functional-programming currying (11)
Me pregunto qué significa esto en F #.
"Una función tomando un número entero,
que devuelve una función que toma un número entero y devuelve un número entero ".
Pero no entiendo esto bien.
¿Alguien puede explicar esto tan claro?
[Actualizar]:
> let f1 x y = x+y ;;
val f1 : int -> int -> int
Qué significa esto ?
una función que toma un entero, que devuelve una función que toma un número entero y devuelve un número entero
La última parte de eso:
una función que toma un número entero y devuelve un número entero
debería ser bastante simple, C # ejemplo:
public int Test(int takesAnInteger) { return 0; }
Así que nos quedamos con
una función que toma un número entero, que regresa (una función como la de arriba)
C # nuevamente:
public int Test(int takesAnInteger) { return 0; }
public int Test2(int takesAnInteger) { return 1; }
public Func<int,int> Test(int takesAnInteger) {
if(takesAnInteger == 0) {
return Test;
} else {
return Test2;
}
}
Ejemplo:
let fba = pown ab //fab = a^b
es una función que toma un int (el exponente) y devuelve una función que eleva su argumento a ese exponente, como
let sqr = f 2
o
let tothepowerofthree = f 3
asi que
sqr 5 = 25
tothepowerofthree 3 = 27
El concepto se llama Función de orden superior y bastante común para la programación funcional.
Las funciones mismas son solo otro tipo de datos. Por lo tanto, puede escribir funciones que devuelven otras funciones. Por supuesto, todavía puede tener una función que toma un int como parámetro y devuelve algo más. Combina los dos y considera el siguiente ejemplo (en python):
def mult_by(a):
def _mult_by(x):
return x*a
return mult_by
mult_by_3 = mult_by(3)
print mylt_by_3(3)
9
(Lo siento por usar Python, pero no sé f #)
El ejemplo canónico de esto es probablemente un "creador de sumadores" - una función que, dado un número (por ejemplo, 3) devuelve otra función que toma un número entero y le agrega el primer número.
Entonces, por ejemplo, en pseudo-código
x = CreateAdder(3)
x(5) // returns 8
x(10) // returns 13
CreateAdder(20)(30) // returns 50
No estoy bastante cómodo en F # para tratar de escribirlo sin verificarlo, pero el C # sería algo así como:
public static Func<int, int> CreateAdder(int amountToAdd)
{
return x => x + amountToAdd;
}
¿Eso ayuda?
EDITAR: Como señaló Bruno, el ejemplo que has dado en tu pregunta es exactamente el ejemplo para el que he dado el código C #, por lo que el pseudocódigo anterior se convertiría en:
let x = f1 3
x 5 // Result: 8
x 10 // Result: 13
f1 20 30 // Result: 50
En F # (y en muchos otros lenguajes funcionales), hay un concepto llamado curried functions. Esto es lo que estás viendo Esencialmente, cada función toma un argumento y devuelve un valor.
Esto parece un poco confuso al principio, porque puedes escribir let add xy = x + y
y parece agregar dos argumentos. Pero en realidad, la función de add
original solo toma el argumento x
. Cuando lo aplica, devuelve una función que toma un argumento ( y
) y tiene el valor x
ya rellenado. Cuando luego aplica esa función, devuelve el número entero deseado.
Esto se muestra en la firma de tipo. Piense en la flecha en una firma tipo que significa "toma la cosa en mi lado izquierdo y devuelve la cosa en mi lado derecho". En el tipo int -> int -> int
, esto significa que toma un argumento de tipo int
- un entero - y devuelve una función de tipo int -> int
- una función que toma un entero y devuelve un entero. Notarás que esto coincide exactamente con la descripción de cómo funcionan las funciones curried anteriormente.
Es posible que desee leer
Es una función que toma un número entero y devuelve una función que toma un número entero y devuelve un número entero.
Esto es funcionalmente equivalente a una función que toma dos enteros y devuelve un entero. Esta forma de tratar funciones que toman múltiples parámetros es común en los lenguajes funcionales y hace que sea fácil aplicar parcialmente una función en un valor.
Por ejemplo, supongamos que hay una función de agregar que toma dos enteros y los suma:
let add x y = x + y
Usted tiene una lista y desea agregar 10 a cada artículo. Aplicaría parcialmente la función de add
al valor 10
. Uniría uno de los parámetros a 10 y dejaría el otro argumento sin consolidar.
let list = [1;2;3;4]
let listPlusTen = List.map (add 10)
Este truco hace que las funciones de composición sean muy fáciles y las hace muy reutilizables. Como puede ver, no necesita escribir otra función que agregue 10 a los elementos de la lista para pasarla al map
. Usted acaba de reutilizar la función de add
.
Generalmente interpreta esto como una función que toma dos enteros y devuelve un entero. Deberías leer sobre el currying .
Ya hay muchas respuestas aquí, pero me gustaría ofrecer otra opinión. A veces, explicar la misma cosa de muchas maneras diferentes te ayuda a ''asimilarlo''.
Me gusta pensar en funciones como "me das algo, y te devolveré algo más"
Entonces, Func<int, string>
dice "me das una int, y te daré una cadena".
También me resulta más fácil pensar en términos de "más tarde": " Cuando me das un int, te doy una cadena". Esto es especialmente importante cuando ve cosas como myfunc = x => y => x + y
(" Cuando le das a curried una x, obtienes algo que cuando lo das ay devuelve x + y").
(Por cierto, supongo que estás familiarizado con C # aquí)
Entonces podríamos expresar su int -> int -> int
ejemplo como Func<int, Func<int, int>>
.
Otra forma en que veo int -> int -> int
es que quita cada elemento de la izquierda proporcionando un argumento del tipo apropiado. Y cuando no tienes más ->
''s, te quedas sin'' laters ''y obtienes un valor.
(Solo por diversión), puede transformar una función que toma todos sus argumentos de una vez en uno que los toma ''progresivamente'' (el término oficial para aplicarlos progresivamente es ''aplicación parcial''), esto se llama ''currying'':
static void Main()
{
//define a simple add function
Func<int, int, int> add = (a, b) => a + b;
//curry so we can apply one parameter at a time
var curried = Curry(add);
//''build'' an incrementer out of our add function
var inc = curried(1); // (var inc = Curry(add)(1) works here too)
Console.WriteLine(inc(5)); // returns 6
Console.ReadKey();
}
static Func<T, Func<T, T>> Curry<T>(Func<T, T, T> f)
{
return a => b => f(a, b);
}
Aquí está mi 2 c. Por defecto, las funciones F # permiten la aplicación parcial o currying. Esto significa que cuando defines esto:
let adder a b = a + b;;
Está definiendo una función que toma un entero y devuelve una función que toma un entero y devuelve un entero o int -> int -> int
. Currying le permite aplicar parcialmente una función para crear otra función:
let twoadder = adder 2;;
//val it: int -> int
El código anterior predice a a 2, de modo que cuando llame a twoadder 3
simplemente agregará dos al argumento.
La sintaxis donde los parámetros de la función están separados por espacio es equivalente a esta sintaxis lambda:
let adder = fun a -> fun b -> a + b;;
Cuál es una manera más legible de descubrir que las dos funciones están realmente encadenadas.
Tipos F #
Comencemos desde el principio.
F # usa la notación de dos puntos (:) para indicar tipos de cosas. Digamos que defines un valor de tipo int
:
let myNumber = 5
F # Interactive entenderá que myNumber
es un número entero, y le dirá esto por:
myNumber : int
que se lee como
myNumber
es de tipoint
Tipos funcionales F #
Hasta aquí todo bien. Vamos a presentar algo más, tipos funcionales . Un tipo funcional es simplemente el tipo de una función . F # usa ->
para denotar un tipo funcional. Esta flecha simboliza que lo que está escrito en su lado izquierdo se transforma en lo que está escrito en su lado derecho.
Consideremos una función simple, que toma un argumento y lo transforma en un solo resultado. Un ejemplo de tal función sería:
isEven : int -> bool
Esto introduce el nombre de la función (a la izquierda de :
y su tipo. Esta línea se puede leer en inglés como:
isEven
es de tipo función que transforma unint
en unbool
.
Tenga en cuenta que para interpretar correctamente lo que se dice, debe hacer una breve pausa justo después de la parte "es de tipo", y luego leer el resto de la oración a la vez, sin detenerse.
En F # las funciones son valores
En F #, las funciones son (casi) no más especiales que los tipos ordinarios . Son cosas que puedes pasar a funciones, regresar de funciones, como bools, ints o strings.
Entonces si tienes:
myNumber : int
isEven : int -> bool
Debería considerar int
e int -> bool
como dos entidades del mismo tipo: tipos. Aquí, myNumber
es un valor de tipo int
, e isEven
es un valor de tipo int -> bool
(esto es lo que intento simbolizar cuando hablo de la breve pausa anterior).
Aplicación de función
Los valores de tipos que contienen ->
también se denominan funciones y tienen poderes especiales: puede aplicar una función a un valor. Así por ejemplo,
isEven myNumber
significa que está aplicando la función llamada isEven
al valor myNumber
. Como puede esperar al inspeccionar el tipo de isEven
, devolverá un valor booleano. Si ha implementado correctamente isEven
, obviamente devolverá false
.
Una función que devuelve un valor de un tipo funcional
Definamos una función genérica para determinar si un número entero es múltiplo de algún otro entero. Podemos imaginar que el tipo de nuestra función será (los paréntesis están aquí para ayudarlo a comprender, pueden o no estar presentes, tienen un significado especial):
isMultipleOf : int -> (int -> bool)
Como puedes adivinar, esto se lee como:
isMultipleOf
es una función de tipo (PAUSE) que transforma una funciónint
en (PAUSE) que transforma unint
en unbool
.
(aquí la (PAUSA) denota las pausas cuando lee en voz alta).
Definiremos esta función más tarde. Antes de eso, veamos cómo podemos usarlo:
let isEven = isMultipleOf 2
F # interactivo respondería:
isEven : int -> bool
que se lee como
isEven
es de tipoint -> bool
Aquí, isEven
tiene type int -> bool
, dado que acabamos de dar el valor 2 ( int
) a isMultipleOf
, que, como ya hemos visto, transforma un int
en un int -> bool
.
Podemos ver esta función isMultipleOf
como una especie de creador de funciones .
Definición de isMultipleOf
Entonces, definamos esta función mística de creación de funciones.
let isMultipleOf n x =
(x % n) = 0
Fácil, ¿eh?
Si escribe esto en F # Interactive, responderá:
isMultipleOf : int -> int -> bool
¿Dónde están los paréntesis?
Tenga en cuenta que no hay paréntesis. Esto no es particularmente importante para ti ahora. Solo recuerda que las flechas son correctas asociativas . Es decir, si tiene
a -> b -> c
deberías interpretarlo como
a -> (b -> c)
El asociativo de derecha a derecha significa que debe interpretar como si hubiera paréntesis alrededor del operador de la derecha. Asi que:
a -> b -> c -> d
debe ser interpretado como
a -> (b -> (c -> d))
Usos de isMultipleOf
Entonces, como has visto, podemos usar isMultipleOf
para crear nuevas funciones:
let isEven = isMultipleOf 2
let isOdd = not << isEven
let isMultipleOfThree = isMultipleOf 3
let endsWithZero = isMultipleOf 10
F # Interactive respondería:
isEven : int -> bool
isOdd : int -> bool
isMultipleOfThree : int -> bool
endsWithZero : int -> bool
Pero puedes usarlo de manera diferente. Si no desea (o no necesita) crear una nueva función, puede usarla de la siguiente manera:
isMultipleOf 10 150
Esto volvería true
, ya que 150 es múltiplo de 10. Esto es exactamente lo mismo que crear la función endsWithZero
y luego aplicarla al valor 150.
En realidad, la aplicación de función se deja asociativa , lo que significa que la línea anterior debe interpretarse como:
(isMultipleOf 10) 150
Es decir, coloca el paréntesis alrededor de la aplicación de función más a la izquierda.
Ahora, si puedes entender todo esto, tu ejemplo (que es el CreateAdder
canónico) ¡debería ser trivial!
Hace algún tiempo, alguien hizo esta pregunta que trata exactamente sobre el mismo concepto, pero en Javascript. En mi respuesta doy dos ejemplos canónicos (CreateAdder, CreateMultiplier) inf Javascript, que son algo más explícitos sobre las funciones de retorno.
Espero que esto ayude.