JavaScript curry: ¿cuáles son las aplicaciones prácticas?

172

No creo que haya asimilado el curry todavía. Entiendo lo que hace y cómo hacerlo. Simplemente no puedo pensar en una situación en la que lo usaría.

¿Dónde usas curry en JavaScript (o dónde lo usan las bibliotecas principales)? La manipulación de DOM o ejemplos generales de desarrollo de aplicaciones son bienvenidos.

Una de las respuestas menciona la animación. Funciones como slideUp, fadeIntomar un elemento como argumentos y normalmente son una función curricular que devuelve la función de orden superior con la "función de animación" predeterminada incorporada. ¿Por qué es eso mejor que simplemente aplicar la función superior con algunos valores predeterminados?

¿Hay algún inconveniente para usarlo?

Como se solicita aquí, hay algunos buenos recursos sobre el curry de JavaScript:

Agregaré más a medida que surjan en los comentarios.


Entonces, según las respuestas, el currículum y la aplicación parcial en general son técnicas de conveniencia.

Si con frecuencia "refina" una función de alto nivel al llamarla con la misma configuración, puede usar la función de nivel superior para crear métodos de ayuda simples y concisos.

Dave Nolan
fuente
¿Puedes agregar un enlace a un recurso que describa qué es el currículum JS? Un tutorial o una publicación de blog sería genial.
Eric Schoonover
2
svendtofte.com es de largo alcance, pero si omite toda la sección de "Un curso intensivo en ML" y comienza de nuevo en "Cómo escribir JavaScript curry", se convierte en una gran introducción al curry en js.
danio
1
Este es un buen punto de partida para comprender qué es realmente el curry y la aplicación parcial: slid.es/gsklee/functional-programming-in-5-minutes
gsklee
1
El enlace a svendtofte.comparece estar muerto: lo encontré en la máquina WayBack en web.archive.org/web/20130616230053/http://www.svendtofte.com/… Lo sentimos, blog.morrisjohns.com/javascript_closures_for_dummies parece estar abajo también
phatskat
1
Por cierto, la versión parcial de Resig es deficiente (ciertamente no "en el dinero") en que probablemente fallará si uno de los argumentos preinicializados ("curry") recibe el valor indefinido . Cualquier persona interesada en una buena función currificación debe recibir el original de Oliver Steele funcitonal.js , ya que no tiene ese problema.
RobG

Respuestas:

35

@Hank Gay

En respuesta al comentario de EmbiggensTheMind:

No puedo pensar en una instancia en la que curry —por sí mismo— sea útil en JavaScript; Es una técnica para convertir llamadas de función con múltiples argumentos en cadenas de llamadas de función con un solo argumento para cada llamada, pero JavaScript admite múltiples argumentos en una sola llamada de función.

Sin embargo, en JavaScript, y supongo que la mayoría de los otros idiomas reales (no el cálculo lambda), se asocia comúnmente con la aplicación parcial. John Resig lo explica mejor , pero lo esencial es que tiene cierta lógica que se aplicará a dos o más argumentos, y solo conoce los valores para algunos de esos argumentos.

Puede usar la aplicación / currículum parcial para corregir esos valores conocidos y devolver una función que solo acepte las incógnitas, para que se invoque más adelante cuando tenga los valores que desea pasar. Esto proporciona una forma ingeniosa de evitar repetirse cuando hubiera estado llamando a los mismos elementos integrados de JavaScript una y otra vez con los mismos valores menos uno. Para robar el ejemplo de John:

String.prototype.csv = String.prototype.split.partial(/,\s*/);
var results = "John, Resig, Boston".csv();
alert( (results[1] == "Resig") + " The text values were split properly" );
Hank Gay
fuente
2
He tratado de explicarlo con algunos ejemplos y demostraciones en mi artículo escrito.
Zaheer Ahmed
66
Esta es realmente una mala respuesta. El curry no tiene nada que ver con la aplicación parcial. El curry permite la composición de funciones. La composición de funciones permite la reutilización de funciones. La reutilización de funciones aumenta la capacidad de mantenimiento del código. ¡Es fácil!
2
@ señor, usted es una muy mala respuesta. El curry obviamente se trata de hacer que las funciones sean más sabrosas. Claramente perdiste el punto.
Callat
112

Aquí hay un uso interesante y práctico de curry en JavaScript que usa cierres :

function converter(toUnit, factor, offset, input) {
    offset = offset || 0;
    return [((offset + input) * factor).toFixed(2), toUnit].join(" ");
}

var milesToKm = converter.curry('km', 1.60936, undefined);
var poundsToKg = converter.curry('kg', 0.45460, undefined);
var farenheitToCelsius = converter.curry('degrees C', 0.5556, -32);

milesToKm(10);            // returns "16.09 km"
poundsToKg(2.5);          // returns "1.14 kg"
farenheitToCelsius(98);   // returns "36.67 degrees C"

Esto se basa en una curryextensión de Function, aunque, como puede ver, solo usa apply(nada demasiado elegante):

Function.prototype.curry = function() {
    if (arguments.length < 1) {
        return this; //nothing to curry with - return function
    }
    var __method = this;
    var args = toArray(arguments);
    return function() {
        return __method.apply(this, args.concat([].slice.apply(null, arguments)));
    }
}
Prisionero CERO
fuente
55
¡Esto es genial! Lo veo similar a la cita Lisp que dice "Lisp es un lenguaje de programación programable"
santiagobasulto
2
Interesante, pero este ejemplo no parece funcionar. offset+inputestará undefined + 1.60936en tu milesToKmejemplo; eso da como resultado NaN.
Nathan Long
3
@Nathan - el desplazamiento no puede ser indefinido - el valor predeterminado es 0
AngusC
66
Por lo que he leído (justo ahora), "curry" normalmente no es parte de la bolsa de trucos de una Función, a menos que esté usando la biblioteca Prototype o la agregue usted mismo. Muy bien, sin embargo.
Roboprog
11
Lo mismo puede lograrse con el método bind () de ES5. Bind crea una nueva función que, cuando se llama, llama a la función original con el contexto de su primer argumento y con la secuencia de argumentos posterior (que precede a cualquiera que se pase a la nueva función). Entonces puede hacer ... var milesToKm = converter.bind (esto, 'km', 1.60936); o var farenheitToCelsius = converter.bind (esto, 'grados C', 0.5556, -32); El primer argumento, el contexto, esto, es irrelevante aquí, por lo que podría pasar indefinido. Por supuesto, necesitaría aumentar el prototipo de la función base con su propio método de enlace para el retroceso no ES5
hacklikecrack
7

Encontré funciones que se parecen a Python functools.partialmás útiles en JavaScript:

function partial(fn) {
  return partialWithScope.apply(this,
    Array.prototype.concat.apply([fn, this],
      Array.prototype.slice.call(arguments, 1)));
}

function partialWithScope(fn, scope) {
  var args = Array.prototype.slice.call(arguments, 2);
  return function() {
    return fn.apply(scope, Array.prototype.concat.apply(args, arguments));
  };
}

¿Por qué querrías usarlo? Una situación común en la que desea utilizar esto es cuando desea vincular thisuna función a un valor:

var callback = partialWithScope(Object.function, obj);

Ahora, cuando se llama la devolución de llamada, thisapunta a obj. Esto es útil en situaciones de eventos o para ahorrar algo de espacio porque generalmente acorta el código.

Curry es similar a parcial con la diferencia de que la función que devuelve el curry solo acepta un argumento (por lo que yo entiendo).

Armin Ronacher
fuente
7

Estar de acuerdo con Hank Gay: es extremadamente útil en ciertos lenguajes de programación funcionales verdaderos, porque es una parte necesaria. Por ejemplo, en Haskell simplemente no puede llevar múltiples parámetros a una función, no puede hacerlo en programación funcional pura. Usted toma un parámetro a la vez y desarrolla su función. En JavaScript es simplemente innecesario, a pesar de ejemplos artificiales como "convertidor". Aquí está el mismo código de convertidor, sin necesidad de curry:

var converter = function(ratio, symbol, input) {
    return (input*ratio).toFixed(2) + " " + symbol;
}

var kilosToPoundsRatio = 2.2;
var litersToUKPintsRatio = 1.75;
var litersToUSPintsRatio = 1.98;
var milesToKilometersRatio = 1.62;

converter(kilosToPoundsRatio, "lbs", 4); //8.80 lbs
converter(litersToUKPintsRatio, "imperial pints", 2.4); //4.20 imperial pints
converter(litersToUSPintsRatio, "US pints", 2.4); //4.75 US pints
converter(milesToKilometersRatio, "km", 34); //55.08 km

Ojalá Douglas Crockford, en "JavaScript: The Good Parts", hubiera mencionado algo de la historia y el uso real del curry en lugar de sus comentarios desprevenidos. Durante mucho tiempo después de leer eso, me quedé atónito, hasta que estudié la programación funcional y me di cuenta de que era de allí.

Después de pensar un poco más, considero que hay un caso de uso válido para cursar en JavaScript: si está tratando de escribir usando técnicas de programación puramente funcionales usando JavaScript. Sin embargo, parece un caso de uso raro.

Byron Katz
fuente
2
Su código es mucho más fácil de entender que el Prisoner Zero y resuelve el mismo problema sin curry ni nada complejo. Tienes 2 pulgares arriba y él tiene casi 100. Ve a la figura.
DR01D
2

No es magia ni nada ... solo una taquigrafía agradable para funciones anónimas.

partial(alert, "FOO!") es equivalente a function(){alert("FOO!");}

partial(Math.max, 0) corresponde a function(x){return Math.max(0, x);}

Las llamadas a parcial ( terminología de MochiKit . Creo que algunas otras bibliotecas dan a las funciones un método .curry que hace lo mismo) se ven un poco mejor y menos ruidosas que las funciones anónimas.

Marijn
fuente
1

En cuanto a las bibliotecas que lo usan, siempre hay funcional .

¿Cuándo es útil en JS? Probablemente las mismas veces es útil en otros idiomas modernos, pero la única vez que puedo ver que lo uso es en combinación con una aplicación parcial.

Hank Gay
fuente
Gracias Hank, ¿puedes ampliar cuándo es útil en general?
Dave Nolan
1

Yo diría que, muy probablemente, toda la biblioteca de animación en JS está usando curry. En lugar de tener que pasar por cada llamada un conjunto de elementos afectados y una función, que describe cómo debe comportarse el elemento, a una función de orden superior que garantice todas las cosas de tiempo, generalmente es más fácil para el cliente liberar, como API pública alguna función como "slideUp", "fadeIn" que toma solo elementos como argumentos, y que son solo algunas funciones curriculares que devuelven la función de orden superior con la "función de animación" predeterminada incorporada.

artilugio
fuente
¿Por qué es mejor curry la función superior en lugar de simplemente llamarla con algunos valores predeterminados?
Dave Nolan
1
Debido a que es mucho más modular poder curry "doMathOperation" con una suma / multiplicación / cuadrado / módulo / otra calucación a voluntad que imaginar todo el "valor predeterminado" que la función superior podría soportar.
gizmo
1

Aquí hay un ejemplo.

Estoy instrumentando un montón de campos con JQuery para poder ver qué están haciendo los usuarios. El código se ve así:

$('#foo').focus(trackActivity);
$('#foo').blur(trackActivity);
$('#bar').focus(trackActivity);
$('#bar').blur(trackActivity);

(Para los usuarios que no son JQuery, digo que cada vez que un par de campos obtienen o pierden el foco, quiero que se llame a la función trackActivity (). También podría usar una función anónima, pero tendría que duplicarla 4 veces, así que lo saqué y lo llamé).

Ahora resulta que uno de esos campos necesita ser manejado de manera diferente. Me gustaría poder pasar un parámetro en una de esas llamadas para pasar a nuestra infraestructura de seguimiento. Con curry, puedo.

William Pietri
fuente
1

Las funciones de JavaScript se llaman lamda en otro lenguaje funcional. Se puede usar para componer una nueva api (función más potente o completa) según la entrada simple de otro desarrollador. El curry es solo una de las técnicas. Puede usarlo para crear una API simplificada para llamar a una API compleja. Si usted es el desarrollador que usa la API simplificada (por ejemplo, usa jQuery para hacer una manipulación simple), no necesita usar curry. Pero si quieres crear la API simplificada, el curry es tu amigo. Debe escribir un marco de JavaScript (como jQuery, mootools) o una biblioteca, luego puede apreciar su poder. Escribí una función de curry mejorada, en http://blog.semanticsworks.com/2011/03/enhanced-curry-method.html. No necesita el método de curry para hacer curry, solo ayuda hacer curry, pero siempre puede hacerlo manualmente escribiendo una función A () {} para devolver otra función B () {}. Para hacerlo más interesante, use la función B () para devolver otra función C ().

Fred Yang
fuente
1

Sé que es un hilo antiguo, pero tendré que mostrar cómo se está utilizando en las bibliotecas de JavaScript:

Usaré la biblioteca lodash.js para describir estos conceptos concretamente.

Ejemplo:

var fn = function(a,b,c){ 
return a+b+c+(this.greet || '); 
}

Aplicación parcial:

var partialFnA = _.partial(fn, 1,3);

Zurra:

var curriedFn = _.curry(fn);

Unión:

var boundFn = _.bind(fn,object,1,3 );//object= {greet: ’!'}

uso:

curriedFn(1)(3)(5); // gives 9 
or 
curriedFn(1,3)(5); // gives 9 
or 
curriedFn(1)(_,3)(2); //gives 9


partialFnA(5); //gives 9

boundFn(5); //gives 9!

diferencia:

después del curry obtenemos una nueva función sin parámetros pre enlazados.

Después de la aplicación parcial, obtenemos una función que está vinculada con algunos parámetros prebound.

en el enlace podemos vincular un contexto que se utilizará para reemplazar 'esto', si no se vincula, el valor predeterminado de cualquier función será el alcance de la ventana.

Consejo: no hay necesidad de reinventar la rueda. La aplicación parcial / encuadernación / curry están muy relacionadas. Puedes ver la diferencia arriba. Use este significado en cualquier lugar y la gente reconocerá lo que está haciendo sin problemas de comprensión, además tendrá que usar menos código.

Shishir Arora
fuente
0

Estoy de acuerdo en que a veces le gustaría poner en marcha la creación de una pseudo-función que siempre tenga el valor del primer argumento completado. Afortunadamente, encontré una nueva biblioteca de JavaScript llamada jPaq (h ttp: // jpaq.org/ ) que proporciona esta funcionalidad. Lo mejor de la biblioteca es el hecho de que puede descargar su propia compilación que contiene solo el código que necesitará.

Clarence Fredericks
fuente
0

Acabo de escribir un ejemplo de jPaq que muestra algunas aplicaciones interesantes de la función curry. Échale un vistazo aquí: Curry Up String Functions

Chris West
fuente
0

Solo quería agregar algunos recursos para Functional.js:

Conferencia / conferencia que explica algunas aplicaciones http://www.youtube.com/watch?v=HAcN3JyQoyY

Biblioteca Functional.js actualizada: https://github.com/loop-recur/FunctionalJS Algunos buenos ayudantes (lo siento, aquí no hay reputación: p): / loop-recur / PreludeJS

He estado usando esta biblioteca mucho recientemente para reducir la repetición en una biblioteca auxiliar de clientes js IRC. Es una gran cosa, realmente ayuda a limpiar y simplificar el código.

Además, si el rendimiento se convierte en un problema (pero esta lib es bastante ligera), es fácil reescribir usando una función nativa.

megawac
fuente
0

Puede usar el enlace nativo para una solución rápida de una línea

function clampAngle(min, max, angle) {
    var result, delta;
    delta = max - min;
    result = (angle - min) % delta;
    if (result < 0) {
        result += delta;
    }
    return min + result;
};

var clamp0To360 = clampAngle.bind(null, 0, 360);

console.log(clamp0To360(405)) // 45

cstuncsik
fuente
0

Otra puñalada, de trabajar con promesas.

(Descargo de responsabilidad: JS noob, proveniente del mundo de Python. Incluso allí, el curry no se usa tanto, pero puede ser útil en ocasiones. Así que activé la función de curry - ver enlaces)

Primero, estoy comenzando con una llamada ajax. Tengo un procesamiento específico que hacer en caso de éxito, pero en caso de fracaso, solo quiero darle al usuario la retroalimentación de que llamar a algo resultó en algún error . En mi código actual, muestro la retroalimentación de error en un panel de arranque, pero solo estoy usando el registro aquí.

He modificado mi URL en vivo para que esto falle.

function ajax_batch(e){
    var url = $(e.target).data("url");

    //induce error
    url = "x" + url;

    var promise_details = $.ajax(
        url,
        {
            headers: { Accept : "application/json" },
            // accepts : "application/json",
            beforeSend: function (request) {
                if (!this.crossDomain) {
                    request.setRequestHeader("X-CSRFToken", csrf_token);
                }
        },
        dataType : "json",
        type : "POST"}
    );
    promise_details.then(notify_batch_success, fail_status_specific_to_batch);
}

Ahora, aquí para decirle al usuario que un lote falló, necesito escribir esa información en el controlador de errores, porque todo lo que está obteniendo es una respuesta del servidor.

Todavía solo tengo la información disponible en el momento de la codificación; en mi caso, tengo varios lotes posibles, pero no sé cuál ha fallado al analizar la respuesta del servidor sobre la URL fallida.

function fail_status_specific_to_batch(d){
    console.log("bad batch run, dude");
    console.log("response.status:" + d.status);
}

Vamos a hacerlo. La salida de la consola es:

consola:

bad batch run, dude utility.js (line 109) response.status:404

Ahora, vamos a cambiar un poco las cosas y usar un controlador de fallo genérico reutilizable, sino también uno que está al curry en tiempo de ejecución tanto con el tiempo código conocido en el contexto de llamada y la información de tiempo de ejecución disponible de evento.

    ... rest is as before...
    var target = $(e.target).text();
    var context = {"user_msg": "bad batch run, dude.  you were calling :" + target};
    var contexted_fail_notification = curry(generic_fail, context); 

    promise_details.then(notify_batch_success, contexted_fail_notification);
}

function generic_fail(context, d){
    console.log(context);
    console.log("response.status:" + d.status);
}

function curry(fn) {
     var slice = Array.prototype.slice,
        stored_args = slice.call(arguments, 1);
     return function () {
        var new_args = slice.call(arguments),
              args = stored_args.concat(new_args);
        return fn.apply(null, args);
     };
}

consola:

Object { user_msg="bad batch run, dude. you were calling :Run ACL now"} utility.js (line 117) response.status:404 utility.js (line 118)

De manera más general, dado lo extendido que está el uso de devolución de llamada en JS, el curry parece una herramienta bastante útil.

https://javascriptweblog.wordpress.com/2010/04/05/curry-cooking-up-tastier-functions/ http://www.drdobbs.com/open-source/currying-and-partial-functions-in- javasc / 231001821? pgno = 2

JL Peyret
fuente