Zastanawiałem się, czy można to zrobić w Javie. Myślę, że nie jest to możliwe bez natywnej obsługi domknięć.
90
Zastanawiałem się, czy można to zrobić w Javie. Myślę, że nie jest to możliwe bez natywnej obsługi domknięć.
Odpowiedzi:
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.
źródło
(def adder5 (partial + 5)) (prn (adder5 4)) (prn adder5 6)
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
źródło
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
źródło
currying
wCurry.curryn
kodzie źródłowym.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).
źródło
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
źródło
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
curried
przykład ;-)).Testy ryczeć zaprezentowania zarówno currying
Function3
doFunction1 => 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 ;-)
źródło
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 } }
źródło
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
źródło
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;
źródło
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.
źródło
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();
źródło
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.
źródło
2 * ?
W Javie można to zrobić za pomocą pętli.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
źródło
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.
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 } }
źródło
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"); } }
źródło