Czy Java obsługuje Currying?


Odpowiedzi:


146

Java 8 (wydana 18 marca 2014) obsługuje curry. Przykładowy kod Java zamieszczony w odpowiedzi przez missingfaktor można przepisać jako:

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

... co jest całkiem miłe. Osobiście, mając dostępną Javę 8, nie widzę powodu, aby używać alternatywnego języka JVM, takiego jak Scala lub Clojure. Zapewniają oczywiście inne funkcje językowe, ale to nie wystarczy, aby uzasadnić koszt przejścia i słabszą obsługę IDE / narzędzi / bibliotek, IMO.


11
Jestem pod wrażeniem Java 8, ale Clojure to fascynująca platforma wykraczająca poza funkcjonalne funkcje języka. Clojure oferuje wysoce wydajne, niezmienne struktury danych i wyrafinowane techniki współbieżności, takie jak oprogramowanie pamięci transakcyjnej.
Wielkanoc Michaela

2
Dzięki za rozwiązanie, nie tyle za język dig :) Słabsza obsługa IDE to problem, ale narzędzia / biblioteki nie są jednoznaczne - wracając do Java 8 po Clojure, naprawdę brakuje mi narzędzi midje, bibliotek takich jak core .async i funkcje językowe, takie jak makra i łatwa składnia funkcjonalna. Przykład curry w clojure:(def adder5 (partial + 5)) (prn (adder5 4)) (prn adder5 6)
Korny

5
Clojure może być świetnym językiem, ale problem polega na tym, że jest po prostu zbyt „obcy” dla większości programistów Java, którzy są przyzwyczajeni tylko do konwencjonalnej składni w stylu C. bardzo trudno w przyszłości zobaczyć znaczącą migrację do Clojure (lub innego alternatywnego języka JVM), szczególnie biorąc pod uwagę, że kilka takich języków istnieje już od wielu lat i tak się nie stało (to samo zjawisko występuje w świecie .NET, gdzie języki takie jak F # pozostają marginalne).
Rogério

2
Muszę głosować przeciw, ponieważ pokazuje prosty przypadek. Wypróbuj taki, który z String tworzy własną klasę, która następnie konwertuje do innej klasy i porównaj ilość kodu
M4ks

11
@ M4ks Pytanie dotyczy tylko tego, czy Java obsługuje currying czy nie, nie chodzi o ilość kodu w porównaniu z innymi językami.
Rogério

67

Currying i częściowe stosowanie jest absolutnie możliwe w Javie, ale ilość wymaganego kodu prawdopodobnie Cię wyłączy.


Trochę kodu pokazującego curry i częściowe zastosowanie w Javie:

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 tutaj jest odpowiednikiem Haskell powyższego kodu Java:

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: Oba są fragmentami kodu wykonywalnego i możesz je wypróbować na stronie ideone.com.
missingfaktor

16
Ta odpowiedź jest nieaktualna od czasu wydania Java 8. Zobacz odpowiedź Rogério, aby uzyskać bardziej zwięzły sposób.
Matthias Braun

15

Istnieje wiele opcji dla Currying z Javą 8. Typ funkcji Javaslang i jOOλ obie oferują Currying po wyjęciu z pudełka (myślę, że było to przeoczenie w JDK), a moduł Cyclops Functions ma zestaw statycznych metod dla Currying JDK Functions i odniesienia do metod. Na przykład

  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” jest również dostępny dla konsumentów. Np. Aby zwrócić metodę z 3 parametrami, a 2 z już zastosowanych, robimy coś podobnego

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

Javadoc


IMO, tak naprawdę nazywa się to curryingw Curry.currynkodzie źródłowym.
Lebecca

13

EDYCJA : Od 2014 i Java 8 programowanie funkcjonalne w Javie jest teraz nie tylko możliwe, ale także nie brzydkie (śmiem powiedzieć, że piękne). Zobacz na przykład odpowiedź Rogerio .

Stara odpowiedź:

Java nie jest najlepszym wyborem, jeśli zamierzasz używać technik programowania funkcjonalnego. Jak napisał missingfaktor, będziesz musiał napisać dość dużą ilość kodu, aby osiągnąć to, co chcesz.

Z drugiej strony nie jesteś ograniczony do Javy na JVM - możesz używać Scala lub Clojure, które są językami funkcjonalnymi (Scala jest w rzeczywistości zarówno funkcjonalna, jak i OO).


8

Currying wymaga zwrócenia funkcji . Nie jest to możliwe w Javie (bez wskaźników do funkcji), ale możemy zdefiniować i zwrócić typ, który zawiera metodę funkcji:

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

Teraz curry prosty podział. Potrzebujemy rozdzielacza :

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

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

Teraz możemy zrobić podział na curry:

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

1
Teraz, gdy skończyłem mój przykład (opracowany od podstaw), okazuje się, że jedyną różnicą w odpowiedzi na brakujące kody jest to, że nie używam klas anonimowych;)
Andreas Dolk

1
@missingfaktor - mea culpa;)
Andreas Dolk

5

Cóż, Scala , Clojure lub Haskell (lub jakikolwiek inny funkcjonalny język programowania ...) są zdecydowanie językami używanymi do curry i innych funkcjonalnych sztuczek.

Mając to na uwadze, z pewnością można curry z Javą bez nadmiernej ilości schematu, którego można by się spodziewać (cóż, wyraźne określenie typów jest bardzo bolesne - wystarczy spojrzeć na curriedprzykład ;-)).

Testy ryczeć zaprezentowania zarówno curryingFunction3 do 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);
}

a także częściowe zastosowanie , chociaż w tym przykładzie nie jest to naprawdę bezpieczne:

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

To jest zaczerpnięte z Proof of Concept, który właśnie zaimplementowałem dla zabawy przed JavaOne jutro za godzinę „bo się nudziłem” ;-) Kod jest dostępny tutaj: https://github.com/ktoso/jcurry

Ogólną ideę można by stosunkowo łatwo rozszerzyć do FunctionN => FunctionM, chociaż „rzeczywiste bezpieczeństwo typów” pozostaje problemem w przypadku częściowego przykładu aplikacji, a przykład curry wymagałby cholernie dużo kodu boilerplaty w jcurry , ale jest to wykonalne.

W sumie jest to wykonalne, ale w Scali jest po wyjęciu z pudełka ;-)


5

Można emulować curry za pomocą 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

Tak, zobacz sam przykład kodu:

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

    }

}

To jest prosty przykład, gdzie curriedAdd jest funkcją curried, która zwraca inną funkcję i może być użyta do częściowego zastosowania parametrów przechowywanych w curried, która jest funkcją samą w sobie. Jest to teraz w pełni stosowane, gdy drukujemy je na ekranie.

Co więcej, później możesz zobaczyć, jak możesz go użyć w stylu JS jako

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

4

Jeszcze jedno spojrzenie na możliwości 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;

Możesz także zdefiniować metody narzędziowe, takie jak ta:

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

Co daje prawdopodobnie bardziej czytelną składnię:

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

3

W Javie zawsze możliwe jest sprawdzenie metody, ale nie obsługuje jej w standardowy sposób. Próba osiągnięcia tego jest skomplikowana i sprawia, że ​​kod jest dość nieczytelny. Java nie jest do tego odpowiednim językiem.


3

Inny wybór dotyczy języka 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);
}

w ten sposób możesz osiągnąć curry

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

Chociaż możesz robić currying w Javie, jest brzydki (ponieważ nie jest obsługiwany) W Javie prostsze i szybsze jest używanie prostych pętli i prostych wyrażeń. Jeśli opublikujesz przykład zastosowania curry, możemy zasugerować alternatywy, które działają tak samo.


3
Co curry ma wspólnego z pętlami ?! Przynajmniej sprawdź termin, zanim odpowiesz na pytanie na jego temat.
missingfaktor

@missingFaktor, funkcje curried są zwykle stosowane do kolekcji. np. list2 = list.apply (curriedFunction), gdzie może znajdować się curriedFunction. 2 * ?W Javie można to zrobić za pomocą pętli.
Peter Lawrey

@Peter: To częściowe zastosowanie, a nie curry. Ani też nie jest specyficzne dla operacji windykacyjnych.
missingfaktor

@missingfaktor, Chodzi mi o to; nie rozłączać się z jakąś konkretną funkcją, ale cofnąć się o krok i spojrzeć na szerszy problem, a najprawdopodobniej istnieje proste rozwiązanie.
Peter Lawrey

@Peter: Jeśli chcesz zakwestionować sedno pytania, umieść swoją uwagę jako komentarz, a nie jako odpowiedź. (IMHO)
missingfaktor

2

To jest biblioteka do curry i częściowego zastosowania w Javie:

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

Obsługuje również destrukturyzację krotek i Map. Wejście do parametrów metody, jak na przykład przekazanie Map.Entry do metody, która przyjmuje 2 parametry, więc Entry.getKey () przejdzie do pierwszego parametru, a Entry.getValue () przejdzie do drugiego parametru

Więcej szczegółów w pliku README


2

Zaletą używania Currying w Javie 8 jest to, że pozwala definiować funkcje wyższego rzędu, a następnie przekazywać funkcje pierwszego rzędu i argumenty funkcji w łańcuchowy, elegancki sposób.

Oto przykład dla rachunku różniczkowego, funkcji pochodnej.

  1. Zdefiniujmy przybliżenie funkcji pochodnej jako (f (x + h) -f (x)) / h . Będzie to funkcja wyższego rzędu
  2. Obliczmy pochodną 2 różnych funkcji, 1 / x , i znormalizowany rozkład Gaussa

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

Tak, zgadzam się z @ Jérôme, curring w Javie 8 nie jest obsługiwany w standardowy sposób, jak w Scali lub innych funkcjonalnych językach programowania.

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");
  }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.