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.
(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
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);
currying
w Curry.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).
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
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 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 ;-)
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
}
}
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
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;
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.
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();
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.
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
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
}
}
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");
}
}