O Java oferece suporte ao Currying?


88

Eu queria saber se existe alguma maneira de puxar isso em Java. Acho que não é possível sem suporte nativo para fechamentos.


4
Para registro, Java 8 agora oferece suporte a currying e aplicativo parcial e tem suporte nativo para encerramentos. Esta é uma pergunta extremamente desatualizada.
Robert Fischer

Respostas:


145

Java 8 (lançado em 18 de março de 2014) oferece suporte ao currying. O exemplo de código Java postado na resposta por missingfaktor pode ser reescrito como:

import java.util.function.*;
import static java.lang.System.out;

// Tested with JDK 1.8.0-ea-b75
public class CurryingAndPartialFunctionApplication
{
   public static void main(String[] args)
   {
      IntBinaryOperator simpleAdd = (a, b) -> a + b;
      IntFunction<IntUnaryOperator> curriedAdd = a -> b -> a + b;

      // Demonstrating simple add:
      out.println(simpleAdd.applyAsInt(4, 5));

      // Demonstrating curried add:
      out.println(curriedAdd.apply(4).applyAsInt(5));

      // Curried version lets you perform partial application:
      IntUnaryOperator adder5 = curriedAdd.apply(5);
      out.println(adder5.applyAsInt(4));
      out.println(adder5.applyAsInt(6));
   }
}

... o que é muito bom. Pessoalmente, com o Java 8 disponível, vejo poucos motivos para usar uma linguagem JVM alternativa, como Scala ou Clojure. Eles fornecem outros recursos de linguagem, é claro, mas isso não é suficiente para justificar o custo de transição e o suporte a IDE / ferramentas / bibliotecas mais fraco, IMO.


11
Estou impressionado com o Java 8, mas Clojure é uma plataforma atraente além dos recursos de linguagem funcional. Clojure oferece estruturas de dados imutáveis ​​altamente eficientes e técnicas sofisticadas de simultaneidade, como memória transacional por software.
Michael Easter

2
Obrigado pela solução, não tanto pela pesquisa de linguagem :) O suporte IDE mais fraco é um problema, mas ferramentas / bibliotecas não são claras - tendo voltado ao Java 8 depois de Clojure, estou realmente sentindo falta de ferramentas midje, bibliotecas como core .async, e recursos de linguagem como macros e sintaxe funcional fácil. O exemplo de currying em clojure:(def adder5 (partial + 5)) (prn (adder5 4)) (prn adder5 6)
Korny,

5
Clojure pode ser uma ótima linguagem, mas o problema é que ela é muito "estranha" para a maioria dos desenvolvedores Java que estão acostumados apenas com a sintaxe convencional do estilo C; é muito difícil ver uma migração significativa para Clojure (ou qualquer outra linguagem JVM alternativa) no futuro, especialmente considerando que várias dessas linguagens já existem há muitos anos e isso não aconteceu (o mesmo fenômeno ocorre no mundo .NET, onde linguagens como F # permanecem marginais).
Rogério

2
Eu tenho que votar negativamente, porque mostra um caso simples. Tente um que da String cria sua própria classe, que depois se converte em alguma outra classe, e compare a quantidade de código
M4ks de

11
@ M4ks A questão é apenas se o Java suporta currying ou não, não é sobre a quantidade de código em comparação com outras linguagens.
Rogério

67

O currying e a aplicação parcial são absolutamente possíveis em Java, mas a quantidade de código necessária provavelmente irá desligá-lo.


Algum código para demonstrar currying e aplicação parcial em Java:

interface Function1<A, B> {
  public B apply(final A a);
}

interface Function2<A, B, C> {
  public C apply(final A a, final B b);
}

class Main {
  public static Function2<Integer, Integer, Integer> simpleAdd = 
    new Function2<Integer, Integer, Integer>() {
      public Integer apply(final Integer a, final Integer b) {
        return a + b;
      }
    };  

  public static Function1<Integer, Function1<Integer, Integer>> curriedAdd = 
    new Function1<Integer, Function1<Integer, Integer>>() {
      public Function1<Integer, Integer> apply(final Integer a) {
        return new Function1<Integer, Integer>() {
          public Integer apply(final Integer b) {
            return a + b;
          }
        };
      }
    };

  public static void main(String[] args) {
    // Demonstrating simple `add`
    System.out.println(simpleAdd.apply(4, 5));

    // Demonstrating curried `add`
    System.out.println(curriedAdd.apply(4).apply(5));

    // Curried version lets you perform partial application 
    // as demonstrated below.
    Function1<Integer, Integer> adder5 = curriedAdd.apply(5);
    System.out.println(adder5.apply(4));
    System.out.println(adder5.apply(6));
  }
}

FWIW aqui é o equivalente Haskell do código Java acima:

simpleAdd :: (Int, Int) -> Int
simpleAdd (a, b) = a + b

curriedAdd :: Int -> Int -> Int
curriedAdd a b = a + b

main = do
  -- Demonstrating simpleAdd
  print $ simpleAdd (5, 4)

  -- Demonstrating curriedAdd
  print $ curriedAdd 5 4

  -- Demostrating partial application
  let adder5 = curriedAdd 5 in do
    print $ adder5 6
    print $ adder5 9

@OP: Ambos são trechos de código executáveis ​​e você pode experimentá-los em ideone.com.
missingfaktor

16
Essa resposta está desatualizada desde o lançamento do Java 8. Veja a resposta de Rogério para uma forma mais concisa.
Matthias Braun

15

Há muitas opções para Currying com Java 8. Tipo de função Javaslang e jOOλ, ambos oferecendo Currying pronto para uso (acho que foi um descuido no JDK), e o módulo de Funções do Cyclops tem um conjunto de métodos estáticos para Currying funções JDK e referências de métodos. Por exemplo

  Curry.curry4(this::four).apply(3).apply(2).apply("three").apply("4");

  public String four(Integer a,Integer b,String name,String postfix){
    return name + (a*b) + postfix;
 }

'Currying' também está disponível para consumidores. Por exemplo, para retornar um método com 3 parâmetros, e 2 daqueles já aplicados, fazemos algo semelhante a este

 return CurryConsumer.curryC3(this::methodForSideEffects).apply(2).apply(2);

Javadoc


IMO, isso é o que realmente chamou curryingno Curry.curryncódigo-fonte.
Lebecca

13

EDIT : A partir de 2014 e Java 8, a programação funcional em Java agora não só é possível, mas também não é feia (atrevo-me a dizer bonita). Veja por exemplo a resposta do Rogério .

Resposta antiga:

Java não é a melhor escolha, se você pretende usar técnicas de programação funcional. Como missfaktor escreveu, você terá que escrever uma grande quantidade de código para conseguir o que deseja.

Por outro lado, você não está restrito a Java na JVM - você pode usar Scala ou Clojure, que são linguagens funcionais (Scala é, na verdade, funcional e OO).


8

Currying requer o retorno de uma função . Isso não é possível com java (sem ponteiros de função), mas podemos definir e retornar um tipo que contém um método de função:

public interface Function<X,Z> {  // intention: f(X) -> Z
   public Z f(X x);
}

Agora vamos caril uma divisão simples. Precisamos de um divisor :

// f(X) -> Z
public class Divider implements Function<Double, Double> {
  private double divisor;
  public Divider(double divisor) {this.divisor = divisor;}

  @Override
  public Double f(Double x) {
    return x/divisor;
  }
}

e um DivideFunction :

// f(x) -> g
public class DivideFunction implements Function<Double, Function<Double, Double>> {
  @Override
  public function<Double, Double> f(Double x) {
    return new Divider(x);
  }

Agora podemos fazer uma divisão com curry:

DivideFunction divide = new DivideFunction();
double result = divide.f(2.).f(1.);  // calculates f(1,2) = 0.5

1
Agora que terminei meu exemplo (desenvolvido do zero), descobri que a única diferença para a resposta de códigos ausentes é que não uso classes anônimas;)
Andreas Dolk

1
@missingfaktor - mea culpa;)
Andreas Dolk

5

Bem, Scala , Clojure ou Haskell (ou qualquer outra linguagem de programação funcional ...) são definitivamente AS linguagens a serem usadas para currying e outros truques funcionais.

Dito isso, certamente é possível trabalhar com Java sem a quantidade excessiva de clichês que se poderia esperar (bem, ter que ser explícito sobre os tipos dói muito - dê uma olhada no curriedexemplo ;-)).

Os ensaios abaixo mostrar tanto, currying um Function3em Function1 => Function1 => Function1:

@Test
public void shouldCurryFunction() throws Exception {
  // given
  Function3<Integer, Integer, Integer, Integer> func = (a, b, c) -> a + b + c;

  // when
  Function<Integer, Function<Integer, Function<Integer, Integer>>> cur = curried(func);

  // then
  Function<Integer, Function<Integer, Integer>> step1 = cur.apply(1);
  Function<Integer, Integer> step2 = step1.apply(2);
  Integer result = step2.apply(3);

  assertThat(result).isEqualTo(6);
}

bem como a aplicação parcial , embora não seja realmente à prova de tipos neste exemplo:

@Test
public void shouldCurryOneArgument() throws Exception {
  // given
  Function3<Integer, Integer, Integer, Integer> adding = (a, b, c) -> a + b + c;

  // when
  Function2<Integer, Integer, Integer> curried = applyPartial(adding, _, _, put(1));

  // then
  Integer got = curried.apply(0, 0);
  assertThat(got).isEqualTo(1);
}

Isso é tirado de uma prova de conceito que acabei de implementar para me divertir antes do JavaOne amanhã em uma hora "porque estava entediado" ;-) O código está disponível aqui: https://github.com/ktoso/jcurry

A ideia geral poderia ser expandida para FunctionN => FunctionM, com relativa facilidade, embora "real typesafety" continue sendo um problema para o exemplo de aplicativo partia e o exemplo currying precisaria de um monte de código boilerplaty rapidamente , mas é factível.

Em suma, é factível, mas no Scala está fora da caixa ;-)


5

É possível emular currying com Java 7 MethodHandles: http://www.tutorials.de/threads/java-7-currying-mit-methodhandles.392397/

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;

public class MethodHandleCurryingExample {
    public static void main(String[] args) throws Throwable {
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        MethodHandle sum = lookup.findStatic(Integer.class, "sum", MethodType.methodType(int.class, new Class[]{int.class, int.class}));
        //Currying
        MethodHandle plus1 = MethodHandles.insertArguments(sum,0,1);
        int result = (int) plus1.invokeExact(2);
        System.out.println(result); // Output: 3
    }
}

5

Sim, veja o exemplo de código para você:

import java.util.function.Function;

public class Currying {

    private static Function<Integer, Function<Integer,Integer>> curriedAdd = a -> b -> a+b ;

    public static void main(String[] args) {

        //see partial application of parameters
        Function<Integer,Integer> curried = curriedAdd.apply(5);
        //This partial applied function can be later used as
        System.out.println("ans of curried add by partial application: "+ curried.apply(6));
        // ans is 11

        //JS example of curriedAdd(1)(3)
        System.out.println("ans of curried add: "+ curriedAdd.apply(1).apply(3));
        // ans is 4

    }

}

Este é um exemplo simples com curriedAdd sendo uma função curried que retorna outra função, e isso pode ser usado para aplicação parcial dos parâmetros armazenados em curried que é uma função em si. Isso agora é mais tarde aplicado totalmente quando imprimimos na tela.

Além disso, mais tarde você pode ver como você pode usá-lo no estilo JS como

curriedAdd.apply(1).apply(2) //in Java
//is equivalent to 
curriedAdd(1)(2) // in JS

4

Mais uma abordagem das possibilidades do Java 8:

BiFunction<Integer, Integer, Integer> add = (x, y) -> x + y;

Function<Integer, Integer> increment = y -> add.apply(1, y);
assert increment.apply(5) == 6;

Você também pode definir métodos utilitários como este:

static <A1, A2, R> Function<A2, R> curry(BiFunction<A1, A2, R> f, A1 a1) {
    return a2 -> f.apply(a1, a2);
}

O que oferece uma sintaxe indiscutivelmente mais legível:

Function<Integer, Integer> increment = curry(add, 1);
assert increment.apply(5) == 6;

3

O currying de um método sempre é possível em Java, mas não oferece suporte de maneira padrão. Tentar fazer isso é complicado e torna o código bastante ilegível. Java não é a linguagem apropriada para isso.


3

Outra opção é aqui para Java 6+

abstract class CurFun<Out> {

    private Out result;
    private boolean ready = false;

    public boolean isReady() {
        return ready;
    }

    public Out getResult() {
        return result;
    }

    protected void setResult(Out result) {
        if (isReady()) {
            return;
        }

        ready = true;
        this.result = result;
    }

    protected CurFun<Out> getReadyCurFun() {
        final Out finalResult = getResult();
        return new CurFun<Out>() {
            @Override
            public boolean isReady() {
                return true;
            }
            @Override
            protected CurFun<Out> apply(Object value) {
                return getReadyCurFun();
            }
            @Override
            public Out getResult() {
                return finalResult;
            }
        };
    }

    protected abstract CurFun<Out> apply(final Object value);
}

então você pode conseguir curry desta maneira

CurFun<String> curFun = new CurFun<String>() {
    @Override
    protected CurFun<String> apply(final Object value1) {
        return new CurFun<String>() {
            @Override
            protected CurFun<String> apply(final Object value2) {
                return new CurFun<String>() {
                    @Override
                    protected CurFun<String> apply(Object value3) {
                        setResult(String.format("%s%s%s", value1, value2, value3));
//                        return null;
                        return getReadyCurFun();
                    }
                };
            }
        };
    }
};

CurFun<String> recur = curFun.apply("1");
CurFun<String> next = recur;
int i = 2;
while(next != null && (! next.isReady())) {
    recur = next;
    next = recur.apply(""+i);
    i++;
}

// The result would be "123"
String result = recur.getResult();

2

Embora você possa fazer Currying em Java, é feio (porque não é suportado) Em Java é mais simples e rápido usar loops simples e expressões simples. Se você postar um exemplo de onde você usaria currying, podemos sugerir alternativas que fazem a mesma coisa.


3
O que currying tem a ver com loops ?! Pelo menos pesquise o termo antes de responder a uma pergunta sobre ele.
missingfaktor

@missingFaktor, funções curried geralmente são aplicadas a coleções. por exemplo, list2 = list.apply (curriedFunction) onde curriedFunction pode estar 2 * ?Em Java, você faria isso com um loop.
Peter Lawrey

@Peter: Isso é aplicação parcial, não currying. E nenhum deles é específico para operações de coleta.
missingfaktor

@missingfaktor, meu ponto é; não se prenda a um recurso específico, mas dê um passo para trás e observe o problema mais amplo e é muito provável que haja uma solução simples.
Peter Lawrey

@Peter: Se você quiser questionar o ponto da pergunta, você deve postar sua observação como um comentário, e não como uma resposta. (IMHO)
missingfaktor

2

Esta é uma biblioteca para currying e aplicação parcial em Java:

https://github.com/Ahmed-Adel-Ismail/J-Curry

Ele também oferece suporte à desestruturação de Tuplas e Map.Entry em parâmetros de método, como, por exemplo, passar um Map.Entry para um método que usa 2 parâmetros, de modo que o Entry.getKey () irá para o primeiro parâmetro, e o Entry.getValue () irá para o segundo parâmetro

Mais detalhes no arquivo README


2

A vantagem de usar Currying no Java 8 é que ele permite definir funções de alta ordem e, em seguida, passar uma função de primeira ordem e argumentos de função de uma forma encadeada e elegante.

Aqui está um exemplo de Cálculo, a função derivada.

  1. Vamos definir a aproximação da função derivada como (f (x + h) -f (x)) / h . Esta será a função de ordem superior
  2. Vamos calcular a derivada de 2 funções diferentes, 1 / x , e a distribuição gaussiana padronizada

1

    package math;

    import static java.lang.Math.*;
    import java.util.Optional;
    import java.util.function.*;

    public class UnivarDerivative
    {
      interface Approximation extends Function<Function<Double,Double>, 
      Function<Double,UnaryOperator<Double>>> {}
      public static void main(String[] args)
      {
        Approximation derivative = f->h->x->(f.apply(x+h)-f.apply(x))/h;
        double h=0.00001f;
        Optional<Double> d1=Optional.of(derivative.apply(x->1/x).apply(h).apply(1.0)); 
        Optional<Double> d2=Optional.of(
        derivative.apply(x->(1/sqrt(2*PI))*exp(-0.5*pow(x,2))).apply(h).apply(-0.00001));
        d1.ifPresent(System.out::println); //prints -0.9999900000988401
        d2.ifPresent(System.out::println); //prints 1.994710003159016E-6
      }
    }

0

Sim, concordo com @ Jérôme, curring em Java 8 não é suportado de forma padrão como em Scala ou outras linguagens de programação funcionais.

public final class Currying {
  private static final Function<String, Consumer<String>> MAILER = (String ipAddress) -> (String message) -> {
    System.out.println(message + ":" + ipAddress );
  };
  //Currying
  private static final Consumer<String> LOCAL_MAILER =  MAILER.apply("127.0.0.1");

  public static void main(String[] args) {
      MAILER.apply("127.1.1.2").accept("Hello !!!!");
      LOCAL_MAILER.accept("Hello");
  }
}
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.