JavaScript curry: quais são as aplicações práticas?


172

Eu não acho que eu grocou currying ainda. Eu entendo o que faz e como fazê-lo. Eu simplesmente não consigo pensar em uma situação que eu usaria.

Onde você está usando o curry em JavaScript (ou onde as principais bibliotecas estão usando)? Manipulação de DOM ou exemplos gerais de desenvolvimento de aplicativos são bem-vindos.

Uma das respostas menciona animação. Funções como slideUp, fadeIntomam um elemento como argumento e são normalmente uma função curry retornando a função de ordem superior com a “função de animação” padrão incorporada. Por que isso é melhor do que apenas aplicar a função superior com alguns padrões?

Existem desvantagens em usá-lo?

Conforme solicitado, aqui estão alguns bons recursos para currying JavaScript:

Vou adicionar mais à medida que surgem nos comentários.


Portanto, de acordo com as respostas, curry e aplicação parcial em geral são técnicas de conveniência.

Se você está freqüentemente “refinando” uma função de alto nível chamando-a com a mesma configuração, pode curry (ou usar a função de nível superior da Resig) para criar métodos auxiliares concisos e simples.


você pode adicionar um link a um recurso que descreve o que é curry JS? um tutorial ou uma postagem no blog seria ótimo.
Eric Schoonover

2
svendtofte.com é longo, mas se você pular a seção inteira de "Um curso intensivo em ML" e começar novamente em "Como escrever JavaScript ao curry", torna-se uma ótima introdução ao curry em js.
Danio

1
Este é um ponto de partida bom para entender o que curry e aplicação parcial realmente é: slid.es/gsklee/functional-programming-in-5-minutes
gsklee

1
O link para svendtofte.comparece estar morto - encontrei-o na máquina do WayBack em web.archive.org/web/20130616230053/http://www.svendtofte.com/… Desculpe, blog.morrisjohns.com/javascript_closures_for_dummies parece estar desativado também
phatskat

1
Aliás, a versão parcial de Resig é deficiente (certamente não "on the money"), pois provavelmente falhará se um dos argumentos pré-inicializados ("curried") receber o valor indefinido . Qualquer pessoa interessada em uma boa função de curry deve obter o original do funcitonal.js de Oliver Steele , pois ele não tem esse problema.
RobG

Respostas:


35

@Hank Gay

Em resposta ao comentário de EmbiggensTheMind:

Não consigo pensar em uma instância em que o curry - por si só - seja útil em JavaScript; é uma técnica para converter chamadas de função com vários argumentos em cadeias de chamadas de função com um único argumento para cada chamada, mas o JavaScript suporta vários argumentos em uma única chamada de função.

No JavaScript - e eu assumo a maioria das outras linguagens reais (não o cálculo lambda) -, ele é geralmente associado à aplicação parcial. John Resig explica melhor , mas o essencial é que tenha alguma lógica que será aplicada a dois ou mais argumentos, e você só conhece o (s) valor (es) de alguns desses argumentos.

Você pode usar aplicação / curry parcial para corrigir esses valores conhecidos e retornar uma função que aceita apenas as incógnitas, a ser invocada posteriormente quando você realmente tiver os valores que deseja passar. Isso fornece uma maneira bacana de evitar se repetir quando você chamaria os mesmos built-ins JavaScript repetidamente, com os mesmos valores, exceto um. Para roubar o exemplo de João:

String.prototype.csv = String.prototype.split.partial(/,\s*/);
var results = "John, Resig, Boston".csv();
alert( (results[1] == "Resig") + " The text values were split properly" );


6
Esta é realmente uma resposta ruim. O curry não tem nada a ver com aplicação parcial. O curry permite a composição da função. A composição de funções permite a reutilização de funções. A reutilização de funções aumenta a manutenção do código. É tão fácil!

2
@tortor senhor, você é uma resposta muito ruim. Curry é obviamente tornar as funções mais saborosas. Você claramente perdeu o ponto.
Callat 19/07/19

112

Aqui está um uso interessante e prático de currying em JavaScript que usa fechamentos :

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"

Isso depende de uma curryextensão de Function, embora, como você pode ver, ele use apenas apply(nada demais):

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)));
    }
}

5
Isso é ótimo! Eu vê-lo semelhante ao quote lisp que diz "Lisp é uma linguagem de programação programável"
santiagobasulto

2
Interessante, mas este exemplo não parece funcionar. offset+inputestará undefined + 1.60936no seu milesToKmexemplo; que resulta em NaN.
Nathan Long

3
@Nathan - offset não pode ser indefinido - o padrão é 0
AngusC:

6
Pelo que li (agora), o "curry" normalmente não faz parte dos truques de uma função, a menos que você esteja usando a biblioteca Prototype ou adicione você mesmo. Muito legal, no entanto.
precisa saber é

11
O mesmo pode ser obtido com o método bind () do ES5. Bind cria uma nova função que, quando chamada, chama a função original com o contexto de seu primeiro argumento e com a sequência subsequente de argumentos (precedendo qualquer passado para a nova função). Então você pode fazer ... var milesToKm = converter.bind (este, 'km', 1,60936); ou var farenheitToCelsius = converter.bind (este, 'graus C', 0,5556, -32); O primeiro argumento, o contexto, é irrelevante aqui, então você pode passar indefinido. Claro que você precisa para aumentar o protótipo da função base com o seu próprio método ligam para não ES5 fallback
hacklikecrack

7

Encontrei funções que se assemelham ao python functools.partialmais útil em 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 que você gostaria de usá-lo? Uma situação comum em que você deseja usar isso é quando deseja vincular thisuma função a um valor:

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

Agora, quando o retorno de chamada é chamado, thisaponte para obj. Isso é útil em situações de evento ou para economizar espaço, pois geralmente reduz o código.

O curry é semelhante ao parcial, com a diferença de que a função que o curry retorna apenas aceita um argumento (tanto quanto eu o entendo).


7

Concordando com Hank Gay - é extremamente útil em certas linguagens de programação funcionais verdadeiras - porque é uma parte necessária. Por exemplo, em Haskell, você simplesmente não pode levar vários parâmetros a uma função - você não pode fazer isso em programação funcional pura. Você pega um parâmetro de cada vez e cria sua função. No JavaScript, é simplesmente desnecessário, apesar de exemplos inventados como "conversor". Aqui está o mesmo código do conversor, sem a necessidade de currying:

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

Desejo muito que Douglas Crockford, em "JavaScript: The Good Parts", tenha mencionado alguma coisa sobre a história e o uso real do curry, em vez de suas observações irrelevantes. Durante muito tempo depois de ler isso, fiquei confuso, até estudar a programação funcional e perceber que é de onde veio.

Depois de pensar um pouco mais, afirmo que há um caso de uso válido para currying em JavaScript: se você estiver tentando escrever usando técnicas de programação funcional pura usando JavaScript. Parece um caso de uso raro.


2
Seu código é muito mais fácil de entender do que o do Prisoner Zero e resolve o mesmo problema sem currying ou qualquer coisa complexa. Você tem 2 polegares para cima e ele tem quase 100. Vá entender.
DR01D

2

Não é mágica nem nada ... apenas uma abreviação agradável para funções anônimas.

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

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

As chamadas para parcial ( terminologia MochiKit . Acho que algumas outras bibliotecas dão às funções um método .curry que faz a mesma coisa) parecem um pouco mais agradáveis ​​e menos barulhentas que as funções anônimas.


1

Quanto às bibliotecas que o utilizam, há sempre Funcional .

Quando é útil em JS? Provavelmente, nas mesmas ocasiões, é útil em outras linguagens modernas, mas a única vez em que consigo me ver usando é em conjunto com a aplicação parcial.


Obrigado Hank - você pode expandir quando é útil em geral?
Dave Nolan

1

Eu diria que, provavelmente, toda a biblioteca de animação em JS está usando currying. Em vez de precisar passar para cada chamada, um conjunto de elementos impactados e uma função, descrevendo como o elemento deve se comportar, para uma função de ordem superior que garantirá todo o tempo necessário, geralmente é mais fácil para o cliente liberar, como API pública funções como "slideUp", "fadeIn" que usa apenas elementos como argumentos e que são apenas algumas funções ao curry que retornam a função de ordem superior com a "função de animação" padrão incorporada.


Por que é melhor curry a função superior, em vez de simplesmente chamá-la com alguns padrões?
Dave Nolan

1
Porque é muito mais modular poder curry uma "doMathOperation" com uma adição / multiplicação / quadrado / módulo / outra calibração no desejo do que imaginar todo o "padrão" que a função superior poderia suportar.
gizmo

1

Aqui está um exemplo.

Estou instrumentando vários campos com o JQuery para que eu possa ver o que os usuários estão fazendo. O código fica assim:

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

(Para usuários não-JQuery, estou dizendo que sempre que alguns campos obtêm ou perdem o foco, desejo que a função trackActivity () seja chamada. Também poderia usar uma função anônima, mas precisaria duplicá-la. 4 vezes, então eu o peguei e o nomeei.)

Agora acontece que um desses campos precisa ser tratado de maneira diferente. Gostaria de passar um parâmetro em uma dessas chamadas a serem repassadas para nossa infraestrutura de rastreamento. Com curry, eu posso.


1

Funções JavaScript é chamado lamda em outra linguagem funcional. Ele pode ser usado para compor uma nova API (função mais poderosa ou complext) com base na entrada simples de outro desenvolvedor. O curry é apenas uma das técnicas. Você pode usá-lo para criar uma API simplificada para chamar uma API complexa. Se você é o desenvolvedor que usa a API simplificada (por exemplo, você usa o jQuery para fazer manipulação simples), não precisa usar curry. Mas se você deseja criar a API simplificada, o curry é seu amigo. Você precisa escrever uma estrutura javascript (como jQuery, mootools) ou biblioteca, para poder apreciar seu poder. Eu escrevi uma função aprimorada de curry, em http://blog.semanticsworks.com/2011/03/enhanced-curry-method.html. Você não precisa do método curry para currying, apenas ajuda a currying, mas sempre pode fazê-lo manualmente, escrevendo uma função A () {} para retornar outra função B () {}. Para torná-lo mais interessante, use a função B () para retornar outra função C ().


1

Eu conheço seu thread antigo, mas terei que mostrar como isso está sendo usado nas bibliotecas javascript:

Usarei a biblioteca lodash.js para descrever esses conceitos concretamente.

Exemplo:

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

Aplicação parcial:

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

Escovando:

var curriedFn = _.curry(fn);

Obrigatório:

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!

diferença:

após o curry, obtemos uma nova função sem parâmetros pré-vinculados.

após aplicação parcial, obtemos uma função que é vinculada a alguns parâmetros pré-encadeados.

na vinculação, podemos vincular um contexto que será usado para substituir 'this'; se não vinculado, o padrão de qualquer função será o escopo da janela.

Aconselhar: Não há necessidade de reinventar a roda. Aplicação parcial / encadernação / currying estão muito relacionados. Você pode ver a diferença acima. Use esse significado em qualquer lugar e as pessoas reconhecerão o que você está fazendo sem problemas de compreensão, além de ter que usar menos código.


0

Concordo que, às vezes, você gostaria de fazer a bola rolar criando uma pseudo-função que sempre terá o valor do primeiro argumento preenchido. Felizmente, me deparei com uma nova biblioteca JavaScript chamada jPaq (h ttp: // jpaq.org/ ), que fornece essa funcionalidade. A melhor coisa da biblioteca é o fato de que você pode fazer o download de sua própria compilação, que contém apenas o código necessário.



0

Só queria adicionar alguns recursos para Functional.js:

Palestra / conferência explicando algumas aplicações http://www.youtube.com/watch?v=HAcN3JyQoyY

Biblioteca Functional.js atualizada: https://github.com/loop-recur/FunctionalJS Alguns bons ajudantes (desculpe, novo aqui, sem reputação: p): / loop-recur / PreludeJS

Eu tenho usado essa biblioteca muito recentemente para reduzir a repetição em uma biblioteca auxiliar de clientes js IRC. É ótimo - realmente ajuda a limpar e simplificar o código.

Além disso, se o desempenho se tornar um problema (mas essa biblioteca é bastante leve), é fácil reescrever usando uma função nativa.


0

Você pode usar a ligação nativa para uma solução rápida de uma linha

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


0

Outra facada, por trabalhar com promessas.

(Isenção de responsabilidade: JS noob, proveniente do mundo Python. Mesmo lá, o curry não é muito usado, mas pode ser útil de vez em quando.

Primeiro, estou começando com uma chamada ajax. Eu tenho algum processamento específico para fazer com sucesso, mas com falha, eu só quero dar ao usuário o feedback de que chamar algo resultou em algum erro . No meu código real, eu exibo o feedback de erro em um painel de inicialização, mas estou apenas usando o log aqui.

Modifiquei meu URL ativo para fazer com que isso falhe.

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);
}

Agora, aqui, para informar ao usuário que um lote falhou, preciso escrever essas informações no manipulador de erros, porque tudo o que está recebendo é uma resposta do servidor.

Ainda tenho apenas as informações disponíveis no momento da codificação - no meu caso, tenho vários lotes possíveis, mas não sei qual deles falhou ao analisar a resposta do servidor sobre o URL com falha.

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

Vamos fazer isso. A saída do console é:

console:

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

Agora, vamos mudar um pouco as coisas e usar um manipulador de falhas genérico reutilizável, mas também um que é acessado em tempo de execução com o contexto de chamada conhecido no momento do código e as informações de tempo de execução disponíveis no 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);
     };
}

console:

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 maneira mais geral, considerando o uso generalizado de retorno de chamada em JS, o curry parece ser uma ferramenta 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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.