Поддерживает ли Java карринг? - PullRequest
85 голосов
/ 26 мая 2011

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

Ответы [ 15 ]

138 голосов
/ 07 февраля 2013

Java 8 (выпущена 18 марта 2014 г.) поддерживает каррирование. Пример Java-кода, размещенного в ответе от пропущенного фактора , можно переписать как:

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 / tooling / library, IMO.

65 голосов
/ 26 мая 2011

Каррирование и частичное применение абсолютно возможно в 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
13 голосов
/ 11 июня 2015

Существует множество вариантов для Curry с Java 8. Типы функций Javaslang и jOOλ, оба предлагают Curry in the box (я думаю, это был недосмотр в 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

13 голосов
/ 26 мая 2011

РЕДАКТИРОВАТЬ : Начиная с 2014 года и Java 8, функциональное программирование на Java теперь не только возможно, но и не безобразно (смею сказать, красиво). Смотрите, например, ответ Рожерио .

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

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

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

8 голосов
/ 26 мая 2011

Curry требует возврата функции . Это невозможно с 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
5 голосов
/ 11 июня 2014

Можно эмулировать карри с помощью 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 голосов
/ 22 сентября 2013

Ну, Scala , Clojure или Haskell (или любой другой функциональный язык программирования ...), безусловно, языки , используемые для каррирования и других функциональных приемов.

Сказав это, безусловно, можно карри с Java без большого количества шаблонов, которые можно было бы ожидать (ну, хотя необходимость быть откровенным в отношении типов очень ранит - просто взгляните на пример curried ;-)),

Тесты, представленные ниже, демонстрируют как карри a 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 это из коробки; -)

3 голосов
/ 12 апреля 2018

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

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 , являющимся функцией карри, которая возвращает другую функцию, и это можно использовать для частичное применение параметров , хранящихся в curried , что само по себе является функцией.Это теперь позже полностью применяется, когда мы печатаем его на экране.

Более того, позже вы можете увидеть, как вы можете использовать его в стиле JS как

curriedAdd.apply(1).apply(2) //in Java
//is equivalent to 
curriedAdd(1)(2) // in JS
3 голосов
/ 14 ноября 2017

Другой выбор здесь для 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();
3 голосов
/ 08 декабря 2016

Еще один подход к возможностям 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;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...