f# functional-programming currying

"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 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 tipo int

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 un int en un bool .

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ón int en (PAUSE) que transforma un int en un bool .

(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 tipo int -> 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.