Мне было интересно, есть ли способ вытащить это на Java. Я думаю, что это невозможно без встроенной поддержки замыканий.
Поддерживает ли Java каррирование?
Ответы (16)
Java 8 (выпущена 18 марта 2014 г.) поддерживает каррирование. Пример кода Java, опубликованный в ответе missfaktor, можно переписать как:
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));
}
}
... что довольно приятно. Лично я, имея Java 8, не вижу причин использовать альтернативный язык JVM, такой как Scala или Clojure. Конечно, они предоставляют другие языковые функции, но этого недостаточно, чтобы оправдать стоимость перехода и более слабую поддержку IDE/инструментов/библиотек, IMO.
(def adder5 (partial + 5)) (prn (adder5 4)) (prn adder5 6)
- person Korny; 09.03.2015
Каррирование и частичное применение абсолютно возможны в Java, но требуемый объем кода, вероятно, вас оттолкнет.
Некоторый код для демонстрации каррирования и частичного применения в 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 здесь является эквивалентом Haskell приведенного выше кода 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
Существует множество вариантов каррирования с Java 8. Тип функций Javaslang и jOOλ предлагают каррирование «из коробки» (я думаю, что это было недосмотром в JDK), и Cyclops Модуль функций имеет набор статических методов для каррирования функций JDK и ссылок на методы. Например.
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;
}
«Каррирование» также доступно для потребителей. Например, чтобы вернуть метод с 3 параметрами и 2 из уже примененных, мы делаем что-то похожее на это
return CurryConsumer.curryC3(this::methodForSideEffects).apply(2).apply(2);
currying
в исходном коде Curry.curryn
.
- person Lebecca; 08.03.2020
EDIT: Начиная с 2014 года и Java 8 функциональное программирование на Java теперь не только возможно, но и не безобразно (осмелюсь сказать, красиво). См., например, ответ Роджерио.
Старый ответ:
Java — не лучший выбор, если вы собираетесь использовать методы функционального программирования. Как писалmissingfaktor, вам придется написать довольно большой объем кода, чтобы добиться того, чего вы хотите.
С другой стороны, вы не ограничены Java на JVM — вы можете использовать Scala или Clojure, которые являются функциональными языками (на самом деле Scala является и функциональным, и объектно-ориентированным).
Каррирование требует возврата функции. Это невозможно с java (без указателей на функции), но мы можем определить и вернуть тип, который содержит метод функции:
public interface Function<X,Z> { // intention: f(X) -> Z
public Z f(X x);
}
Теперь давайте выполним простое деление. Нам нужен разделитель:
// 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;
}
}
и 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);
}
Теперь мы можем выполнить каррированное деление:
DivideFunction divide = new DivideFunction();
double result = divide.f(2.).f(1.); // calculates f(1,2) = 0.5
Да, смотрите пример кода сами:
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
}
}
Это простой пример, в котором curriedAdd является каррированной функцией, которая возвращает другую функцию, и ее можно использовать для частичного применения параметров, хранящихся в карриед. которая сама по себе является функцией. Теперь это позже применяется полностью, когда мы печатаем его на экране.
Более того, позже вы можете увидеть, как вы можете использовать его в стиле JS, как
curriedAdd.apply(1).apply(2) //in Java
//is equivalent to
curriedAdd(1)(2) // in JS
Что ж, Scala, Clojure или Haskell (или любой другой язык функционального программирования...) определенно являются ТЕМ языками, которые можно использовать для каррирования и других функциональных трюков.
С учетом сказанного, безусловно, можно каррировать с помощью Java без большого количества шаблонов, которые можно было бы ожидать (ну, необходимость быть явным в отношении типов очень утомительна, хотя - просто взгляните на пример curried
;-)).
Приведенные ниже тесты демонстрируют оба варианта: каррирование Function3
в 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);
}
а также частичное применение, хотя в этом примере это не совсем типобезопасно:
@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);
}
Это взято из Proof Of Concept, который я только что реализовал для развлечения перед JavaOne завтра через час "потому что мне было скучно" ;-) Код доступен здесь: https://github.com/ktoso/jcurry
Общая идея может быть относительно легко расширена до FunctionN => FunctionM, хотя «настоящая безопасность типов» остается проблемой для примера приложения partia, а для примера каррирования потребуется чертовски много шаблонного кода в jcurry, но это выполнимо.
В целом, это выполнимо, но в Scala это из коробки ;-)
Можно эмулировать каррирование с помощью MethodHandles Java 7: 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
}
}
Еще один взгляд на возможности 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;
Вы также можете определить служебные методы, подобные этому:
static <A1, A2, R> Function<A2, R> curry(BiFunction<A1, A2, R> f, A1 a1) {
return a2 -> f.apply(a1, a2);
}
Что дает вам, возможно, более читаемый синтаксис:
Function<Integer, Integer> increment = curry(add, 1);
assert increment.apply(5) == 6;
Каррирование метода всегда возможно в Java, но не поддерживается стандартным способом. Попытка добиться этого сложна и делает код довольно нечитаемым. Java не подходит для этого.
Другой вариант здесь для 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);
}
тогда вы могли бы добиться каррирования таким образом
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();
Хотя вы можете выполнять каррирование в Java, это уродливо (потому что оно не поддерживается). В Java проще и быстрее использовать простые циклы и простые выражения. Если вы опубликуете пример использования каррирования, мы можем предложить альтернативы, которые делают то же самое.
2 * ?
В Java вы бы сделали это с помощью цикла.
- person Peter Lawrey; 26.05.2011
Это библиотека для каррирования и частичного применения на Java:
https://github.com/Ahmed-Adel-Ismail/J-Curry
Он также поддерживает деструктурирование Tuples и Map.Entry в параметры метода, например, передачу Map.Entry методу, который принимает 2 параметра, поэтому Entry.getKey() перейдет к первому параметру, а Entry.getValue() пойдет для второго параметра
Подробнее в файле README
Преимущество использования каррирования в Java 8 заключается в том, что оно позволяет вам определять функции высокого порядка, а затем передавать функцию первого порядка и аргументы функции цепочкой и элегантным способом.
Вот пример исчисления, производной функции.
- Определим аппроксимацию производной функции как (f(x+h)-f(x))/h. Это будет функция высокого порядка
- Давайте вычислим производную двух разных функций, 1/x, и стандартизированное распределение Гаусса.
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
}
}
Да, я согласен с @Jérôme, curring в Java 8 не поддерживается стандартным образом, как в Scala или других функциональных языках программирования.
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");
}
}
В то время как все остальные ответы сосредоточены на конкретных примерах, я все же хотел предоставить общее решение для превращения двоичных функций в каррированные.
private static <A, B, C> Function<A, Function<B, C>> Curry(BiFunction<A, B, C> f) {
return a -> b -> f.apply(a, b);
}