Поддерживает ли Java каррирование?

Мне было интересно, есть ли способ вытащить это на Java. Я думаю, что это невозможно без встроенной поддержки замыканий.


person user855    schedule 26.05.2011    source источник
comment
Для справки, Java 8 теперь поддерживает каррирование и частичное применение, а также имеет встроенную поддержку замыканий. Это дико устаревший вопрос.   -  person Robert Fischer    schedule 26.06.2014


Ответы (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.

person Rogério    schedule 07.02.2013
comment
Я впечатлен Java 8, но Clojure — привлекательная платформа, выходящая за рамки функций функционального языка. Clojure предлагает высокоэффективные неизменяемые структуры данных и сложные методы параллелизма, такие как программно-транзакционная память. - person Michael Easter; 21.02.2015
comment
Спасибо за решение, не столько за раскопки языка :) Более слабая поддержка IDE является проблемой, но инструменты/библиотеки не ясны - вернувшись к Java 8 после Clojure, мне действительно не хватает инструментов midje, библиотек, таких как ядро .async, а также функции языка, такие как макросы и простой функциональный синтаксис. Пример каррирования в clojure: (def adder5 (partial + 5)) (prn (adder5 4)) (prn adder5 6) - person Korny; 09.03.2015
comment
Clojure может быть отличным языком, но проблема в том, что он слишком чужд большинству Java-разработчиков, которые привыкли только к обычному синтаксису в стиле C; очень сложно увидеть существенный переход на Clojure (или любой другой альтернативный язык JVM) в будущем, особенно если учесть, что несколько таких языков уже существуют много лет, а этого не произошло (такое же явление происходит в мире .NET, где такие языки, как F#, остаются маргинальными). - person Rogério; 09.03.2015
comment
что все еще уродливо и, вероятно, просто для удовольствия - person zinking; 30.09.2015
comment
Я должен понизить голос, потому что он показывает простой случай. Попробуйте тот, который из String создает ваш собственный класс, который затем преобразуется в какой-либо другой класс, и сравните объем кода. - person M4ks; 27.06.2016
comment
@M4ks Вопрос только в том, поддерживает ли Java каррирование или нет, а не в количестве кода по сравнению с другими языками. - person Rogério; 27.06.2016
comment
На самом деле есть много причин не использовать Java: меньшая разборчивость кода, беспорядок в примитивах, отсутствие сопоставления с образцом, отсутствие классов case, отсутствие реальных замыканий, многословие, отсутствие параметров по умолчанию, отсутствие вывода типов, отсутствие неявных значений, отсутствие неизменяемости по умолчанию, и неизменяемый обычно по ссылке, а не по структуре, нет хорошей модели параллелизма, нет буквального определения кортежей и т. д. и т. д. Если вы можете выбрать, выберите Scala. Если вы не можете, то Java 8 не так уж и плоха. - person Jose Romero; 27.10.2016
comment
Если бы я мог выбрать (примите это к сведению, реальность такова, что мы почти всегда мало или вообще не имеем права голоса в таких вопросах), я бы выбрал Kotlin, гораздо более удобный для разработчиков язык, чем академический Scala. (по крайней мере, это мое восприятие). - person Rogério; 27.10.2016

Каррирование и частичное применение абсолютно возможны в 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
person missingfaktor    schedule 26.05.2011
comment
@OP: Оба являются исполняемыми фрагментами кода, и вы можете попробовать их на ideone.com. - person missingfaktor; 26.05.2011
comment
Этот ответ устарел с момента выпуска Java 8. См. более краткий ответ Рожерио. - person Matthias Braun; 22.03.2014

Существует множество вариантов каррирования с 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);

Javadoc

person John McClean    schedule 11.06.2015
comment
ИМО, это то, что на самом деле называлось currying в исходном коде Curry.curryn. - person Lebecca; 08.03.2020

EDIT: Начиная с 2014 года и Java 8 функциональное программирование на Java теперь не только возможно, но и не безобразно (осмелюсь сказать, красиво). См., например, ответ Роджерио.

Старый ответ:

Java — не лучший выбор, если вы собираетесь использовать методы функционального программирования. Как писалmissingfaktor, вам придется написать довольно большой объем кода, чтобы добиться того, чего вы хотите.

С другой стороны, вы не ограничены Java на JVM — вы можете использовать Scala или Clojure, которые являются функциональными языками (на самом деле Scala является и функциональным, и объектно-ориентированным).

person Xaerxess    schedule 26.05.2011

Каррирование требует возврата функции. Это невозможно с 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
person Andreas Dolk    schedule 26.05.2011
comment
Теперь, когда я закончил свой пример (разработанный с нуля), оказалось, что единственное отличие от ответа на отсутствующие коды заключается в том, что я не использую анонимные классы;) - person Andreas Dolk; 26.05.2011
comment
@missingfaktor - моя вина ;) - person Andreas Dolk; 26.05.2011

Да, смотрите пример кода сами:

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
person Rishabh Agarwal    schedule 12.04.2018

Что ж, 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 это из коробки ;-)

person Konrad 'ktoso' Malawski    schedule 21.09.2013

Можно эмулировать каррирование с помощью 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
    }
}
person Thomas Darimont    schedule 11.06.2014

Еще один взгляд на возможности 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;
person Natix    schedule 08.12.2016

Каррирование метода всегда возможно в Java, но не поддерживается стандартным способом. Попытка добиться этого сложна и делает код довольно нечитаемым. Java не подходит для этого.

person Jérôme Verstrynge    schedule 26.05.2011

Другой вариант здесь для 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();
person John Lee    schedule 14.11.2017

Хотя вы можете выполнять каррирование в Java, это уродливо (потому что оно не поддерживается). В Java проще и быстрее использовать простые циклы и простые выражения. Если вы опубликуете пример использования каррирования, мы можем предложить альтернативы, которые делают то же самое.

person Peter Lawrey    schedule 26.05.2011
comment
Какое отношение каррирование имеет к циклам?! По крайней мере, посмотрите термин, прежде чем отвечать на вопрос о нем. - person missingfaktor; 26.05.2011
comment
@missingFaktor, каррированные функции обычно применяются к коллекциям. например list2 = list.apply(curriedFunction), где curriedFunction может быть 2 * ? В Java вы бы сделали это с помощью цикла. - person Peter Lawrey; 26.05.2011
comment
@Peter: Это частичное применение, а не каррирование. И ни один из них не относится к операциям сбора. - person missingfaktor; 26.05.2011
comment
@missingfaktor, я хочу сказать; не зацикливаться на конкретной функции, а сделать шаг назад и посмотреть на более широкую проблему, и, скорее всего, будет простое решение. - person Peter Lawrey; 26.05.2011
comment
@Peter: Если вы хотите поставить под сомнение суть вопроса, вы должны опубликовать свое замечание как комментарий, а не как ответ. (ПО МОЕМУ МНЕНИЮ) - person missingfaktor; 26.05.2011
comment
@missingfaktor, вы правы, но в моем случае я хотел дать то, что я считаю ответом в Java на реальную проблему, заключающуюся в использовании методов, которые хорошо работают в 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

person Ahmed Adel Ismail    schedule 25.03.2018

Преимущество использования каррирования в Java 8 заключается в том, что оно позволяет вам определять функции высокого порядка, а затем передавать функцию первого порядка и аргументы функции цепочкой и элегантным способом.

Вот пример исчисления, производной функции.

  1. Определим аппроксимацию производной функции как (f(x+h)-f(x))/h. Это будет функция высокого порядка
  2. Давайте вычислим производную двух разных функций, 1/x, и стандартизированное распределение Гаусса.

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
      }
    }
person Jose Luis Soto Posada    schedule 14.10.2018

Да, я согласен с @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");
  }
}
person Ajeet    schedule 22.01.2019

В то время как все остальные ответы сосредоточены на конкретных примерах, я все же хотел предоставить общее решение для превращения двоичных функций в каррированные.

private static <A, B, C> Function<A, Function<B, C>> Curry(BiFunction<A, B, C> f) {
    return a -> b -> f.apply(a, b);
}
person PaulProgrammerNoob    schedule 24.11.2020