functional-programming - currying es6
¿Qué es ''Currying''? (15)
Aquí hay un ejemplo concreto:
Supongamos que tiene una función que calcula la fuerza gravitatoria que actúa sobre un objeto. Si no conoces la fórmula, puedes encontrarla here . Esta función toma en los tres parámetros necesarios como argumentos.
Ahora, estando en la tierra, solo quieres calcular fuerzas para los objetos en este planeta. En un lenguaje funcional, podría pasar la masa de la tierra a la función y luego evaluarla parcialmente. Lo que obtendrías es otra función que toma solo dos argumentos y calcula la fuerza gravitacional de los objetos en la tierra. Esto se llama currying.
He visto referencias a funciones de curry en varios artículos y blogs, pero no puedo encontrar una buena explicación (o al menos una que tenga sentido)
Aquí hay un ejemplo de juguete en Python:
>>> from functools import partial as curry
>>> # Original function taking three parameters:
>>> def display_quote(who, subject, quote):
print who, ''said regarding'', subject + '':''
print ''"'' + quote + ''"''
>>> display_quote("hoohoo", "functional languages",
"I like Erlang, not sure yet about Haskell.")
hoohoo said regarding functional languages:
"I like Erlang, not sure yet about Haskell."
>>> # Let''s curry the function to get another that always quotes Alex...
>>> am_quote = curry(display_quote, "Alex Martelli")
>>> am_quote("currying", "As usual, wikipedia has a nice summary...")
Alex Martelli said regarding currying:
"As usual, wikipedia has a nice summary..."
(Solo se usa la concatenación a través de + para evitar la distracción para los programadores que no son Python).
Edición para añadir:
Consulte http://docs.python.org/library/functools.html?highlight=partial#functools.partial , que también muestra la distinción parcial entre objeto y función en la forma en que Python implementa esto.
Aquí puede encontrar una explicación simple de la implementación de currying en C #. En los comentarios, he intentado mostrar cómo el curry puede ser útil:
public static class FuncExtensions {
public static Func<T1, Func<T2, TResult>> Curry<T1, T2, TResult>(this Func<T1, T2, TResult> func)
{
return x1 => x2 => func(x1, x2);
}
}
//Usage
var add = new Func<int, int, int>((x, y) => x + y).Curry();
var func = add(1);
//Obtaining the next parameter here, calling later the func with next parameter.
//Or you can prepare some base calculations at the previous step and then
//use the result of those calculations when calling the func multiple times
//with different input parameters.
int result = func(1);
Como todas las otras respuestas, el curry ayuda a crear funciones parcialmente aplicadas. Javascript no proporciona soporte nativo para currying automático. Por lo tanto, los ejemplos proporcionados anteriormente pueden no ayudar en la codificación práctica. Hay un excelente ejemplo en livescript (que básicamente compila a js) http://livescript.net/
times = (x, y) --> x * y
times 2, 3 #=> 6 (normal use works as expected)
double = times 2
double 5 #=> 10
En el ejemplo anterior, cuando le ha dado menos argumentos, livescript genera una nueva función de curry para usted (doble)
Curry puede simplificar su código. Esta es una de las principales razones para usar esto. Currying es un proceso de conversión de una función que acepta n argumentos en n funciones que aceptan solo un argumento.
El principio es pasar los argumentos de la función pasada, usando la propiedad de cierre (cierre), almacenarlos en otra función y tratarlos como un valor de retorno, y estas funciones forman una cadena, y los argumentos finales se pasan para completar la operacion.
El beneficio de esto es que puede simplificar el procesamiento de parámetros al tratar con un parámetro a la vez, lo que también puede mejorar la flexibilidad y la legibilidad del programa. Esto también hace que el programa sea más manejable. Además, dividir el código en partes más pequeñas lo haría fácil de reutilizar.
Por ejemplo:
function curryMinus(x)
{
return function(y)
{
return x - y;
}
}
var minus5 = curryMinus(1);
minus5(3);
minus5(5);
También puedo hacer ...
var minus7 = curryMinus(7);
minus7(3);
minus7(5);
Esto es muy bueno para hacer un código complejo limpio y manejar métodos no sincronizados, etc.
El curry es cuando se desglosa una función que toma múltiples argumentos en una serie de funciones que forman parte de los argumentos. Aquí hay un ejemplo en JavaScript:
function add (a, b) {
return a + b;
}
add(3, 4); // returns 7
Esta es una función que toma dos argumentos, a y b, y devuelve su suma. Ahora vamos a curry esta función:
function add (a) {
return function (b) {
return a + b;
}
}
Esta es una función que toma un argumento, a, y devuelve una función que toma otro argumento, b, y esa función devuelve su suma.
add(3)(4);
var add3 = add(3);
add3(4);
La primera instrucción devuelve 7, como la instrucción add (3, 4). La segunda declaración define una nueva función llamada add3 que agregará 3 a su argumento. Esto es lo que algunas personas pueden llamar un cierre. La tercera declaración utiliza la operación add3 para agregar 3 a 4, lo que nuevamente produce 7 como resultado.
El currying es una transformación que se puede aplicar a las funciones para permitirles tomar un argumento menos que antes.
Por ejemplo, en F # puedes definir una función así:
let f x y z = x + y + z
Aquí la función f toma los parámetros x, y, y los suma, así que:
f 1 2 3
Devuelve 6.
Por nuestra definición, podemos definir la función de curry para f: -
let curry f = fun x -> f x
Donde ''fun x -> fx'' es una función lambda equivalente a x => f (x) en C #. Esta función ingresa la función que desea curry y devuelve una función que toma un solo argumento y devuelve la función especificada con el primer argumento establecido en el argumento de entrada.
Usando nuestro ejemplo anterior podemos obtener un curry de f así: -
let curryf = curry f
Entonces podemos hacer lo siguiente:
let f1 = curryf 1
Lo que nos proporciona una función f1 que es equivalente a f1 yz = 1 + y + z. Esto significa que podemos hacer lo siguiente:
f1 2 3
Lo que devuelve 6.
Este proceso se confunde a menudo con la ''aplicación de función parcial'' que se puede definir de la siguiente manera:
let papply f x = f x
Aunque podemos extenderlo a más de un parámetro, es decir:
let papply2 f x y = f x y
let papply3 f x y z = f x y z
etc.
Una aplicación parcial tomará la función y el (los) parámetro (s) y devolverá una función que requiera uno o más parámetros menos, y como muestran los dos ejemplos anteriores, se implementa directamente en la definición estándar de la función F # para que podamos lograr el resultado anterior:
let f1 = f 1
f1 2 3
Lo que devolverá un resultado de 6.
En conclusión:-
La diferencia entre la aplicación de curry y la función parcial es que:
Currying toma una función y proporciona una nueva función que acepta un solo argumento y devuelve la función especificada con su primer argumento establecido a ese argumento. Esto nos permite representar funciones con múltiples parámetros como una serie de funciones de un solo argumento . Ejemplo:-
let f x y z = x + y + z
let curryf = curry f
let f1 = curryf 1
let f2 = curryf 2
f1 2 3
6
f2 1 3
6
La aplicación de función parcial es más directa: toma una función y uno o más argumentos y devuelve una función con los primeros n argumentos establecidos en los n argumentos especificados. Ejemplo:-
let f x y z = x + y + z
let f1 = f 1
let f2 = f 2
f1 2 3
6
f2 1 3
6
En un álgebra de funciones, tratar con funciones que toman múltiples argumentos (o un argumento equivalente que es una tupla N) es un tanto poco elegante, pero, como Moisés Schönfinkel (y, independientemente, Haskell Curry) demostraron, no es necesario: todo lo que Necesidad son funciones que toman un argumento.
Entonces, ¿cómo lidias con algo que naturalmente expresas como, por ejemplo, f(x,y)
? Bueno, tomas eso como equivalente a f(x)(y)
- f(x)
, llámalo g
, es una función, y aplicas esa función a y
. En otras palabras, solo tiene funciones que toman un argumento, pero algunas de esas funciones devuelven otras funciones (que TAMBIÉN toma un argumento ;-).
Como es habitual, wikipedia tiene una buena entrada resumida sobre esto, con muchos punteros útiles (que probablemente incluyen algunos sobre sus idiomas favoritos ;-), así como un tratamiento matemático ligeramente más riguroso.
Encontré este artículo, y el artículo al que hace referencia, es útil para entender mejor el currying: http://blogs.msdn.com/wesdyer/archive/2007/01/29/currying-and-partial-function-application.aspx
Como mencionaron los demás, es solo una forma de tener una función de un solo parámetro.
Esto es útil porque no tiene que asumir cuántos parámetros se pasarán, por lo que no necesita un parámetro 2, 3 parámetros y 4 funciones de parámetros.
Para dar un ejemplo real (y potencialmente útil) de curry, vea cómo puede hacer llamadas de servidor en javascript con la biblioteca fetch
Get(url) {
let fullUrl = toFullUrl(url);
let promise = getPromiseForFetchWithToken((token) => {
let headers = Object.assign(
getDefaultHeaders(token),
jsonHeaders);
let config = {
method: "GET",
headers: headers
};
return fetch(fullUrl, config);
});
return promise;
}
Donde getPromiseForFetchWithToken
es una función al curry que devuelve una Promise
con el resultado de la búsqueda, que se muestra a continuación:
function getPromiseForFetchWithToken(tokenConsumingFetch) {
function resolver(resolve, reject) {
let token = localStorage.getItem("token");
tokenConsumingFetch(token)
.then(checkForError)
.then((response) => {
if (response) resolve(response);
})
.catch(reject);
}
var promise = new Promise(resolver);
return promise;
}
Esto le permite esperar la llamada de la función Get
y luego manejar adecuadamente el valor de retorno independientemente de cuál sea, puede reutilizar la función getPromiseForFetchWithToken
cualquier lugar que necesite para realizar una llamada al servidor que deba incluir un token de portador. (Poner, Eliminar, Publicar, etc.)
Puede ser una forma de usar funciones para hacer otras funciones.
En javascript:
let add = function(x){
return function(y){
return x + y
};
};
Nos permitiría llamarlo así:
let addTen = add(10);
Cuando esto se ejecuta, el 10
se pasa como x
;
let add = function(10){
return function(y){
return 10 + y
};
};
lo que significa que se nos devuelve esta función:
function(y) { 10 + y };
Así que cuando llamas
addTen();
realmente estás llamando
function(y) { 10 + y };
Así que si haces esto:
addTen(4)
es lo mismo que
function(4) { 10 + 4} // 14
Entonces, nuestro addTen()
siempre agrega diez a lo que pasamos. Podemos hacer funciones similares de la misma manera:
let addTwo = add(2) // addTwo(); will add two to whatever you pass in
let addSeventy = add(70) // ... and so on...
Si entiendes partial
estás a mitad de camino. La idea de partial
es aplicar previamente argumentos a una función y devolver una nueva función que solo desea los argumentos restantes. Cuando se llama a esta nueva función, incluye los argumentos precargados junto con los argumentos que se le suministraron.
En Clojure +
es una función, pero para dejar las cosas muy claras:
(defn add [a b] (+ a b))
Puede ser consciente de que la función inc
simplemente agrega 1 a cualquier número que haya pasado.
(inc 7) # => 8
Vamos a construirlo nosotros mismos usando partial
:
(def inc (partial add 1))
Aquí devolvemos otra función que tiene 1 cargado en el primer argumento de add
. Como add
toma dos argumentos, la nueva función inc
solo quiere el argumento b
, no 2 argumentos como antes, ya que 1 ya se ha aplicado parcialmente . Por lo tanto, partial
es una herramienta desde la cual se crean nuevas funciones con valores predeterminados que se presuponen. Es por eso que en un lenguaje funcional las funciones a menudo ordenan los argumentos de general a específico. Esto facilita la reutilización de tales funciones a partir de las cuales construir otras funciones.
Ahora imagine que el lenguaje fuera lo suficientemente inteligente como para comprender introspectivamente que add
dos argumentos deseaban. Cuando le pasamos un argumento, en lugar de obstaculizarnos, ¿qué pasaría si la función aplicara parcialmente el argumento que aprobamos en nuestro nombre, entendiendo que probablemente queríamos proporcionar el otro argumento más adelante? Entonces podríamos definir inc
sin usar explícitamente partial
.
(def inc (add 1)) #partial is implied
Así se comportan algunos idiomas. Es excepcionalmente útil cuando uno desea componer funciones en transformaciones más grandes. Esto llevaría a uno a los transductores.
Un ejemplo de curry sería al tener funciones, solo conoces uno de los parámetros en este momento:
Por ejemplo:
func aFunction(str: String) {
let callback = callback(str) // signature now is `NSData -> ()`
performAsyncRequest(callback)
}
func callback(str: String, data: NSData) {
// Callback code
}
func performAsyncRequest(callback: NSData -> ()) {
// Async code that will call callback with NSData as parameter
}
Aquí, como no conoce el segundo parámetro para la devolución de llamada cuando lo envía a performAsyncRequest(_:)
, tendría que crear otro lambda / cierre para enviar ese a la función.
Una función al curry es una función de varios argumentos reescritos de manera tal que acepta el primer argumento y devuelve una función que acepta el segundo argumento y así sucesivamente. Esto permite que las funciones de varios argumentos tengan algunos de sus argumentos iniciales parcialmente aplicados.
Una función al curry se aplica a múltiples listas de argumentos, en lugar de solo una.
Aquí hay una función regular sin currículum, que agrega dos parámetros Int, x e y:
scala> def plainOldSum(x: Int, y: Int) = x + y
plainOldSum: (x: Int,y: Int)Int
scala> plainOldSum(1, 2)
res4: Int = 3
Aquí está la función similar que está al curry. En lugar de una lista de dos parámetros Int, aplique esta función a dos listas de un parámetro Int cada una:
scala> def curriedSum(x: Int)(y: Int) = x + y
curriedSum: (x: Int)(y: Int)Intscala> second(2)
res6: Int = 3
scala> curriedSum(1)(2)
res5: Int = 3
Lo que está sucediendo aquí es que cuando curriedSum
, en realidad recibes dos invocaciones de funciones tradicionales una detrás de la otra. La primera invocación de función toma un solo parámetro Int llamado x
, y devuelve un valor de función para la segunda función. Esta segunda función toma el parámetro int y
.
Aquí hay una función llamada first
que hace en espíritu lo que haría la primera invocación de función tradicional de curriedSum
:
scala> def first(x: Int) = (y: Int) => x + y
first: (x: Int)(Int) => Int
Aplicar 1 a la primera función, en otras palabras, invocar la primera función y pasar a 1 genera la segunda función:
scala> val second = first(1)
second: (Int) => Int = <function1>
Al aplicar 2 a la segunda función se obtiene el resultado:
scala> second(2)
res6: Int = 3