Чи підтримує Java каррінг?


90

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


4
Для запису Java 8 тепер підтримує каррінг та часткове застосування та має вбудовану підтримку закриття. Це дико застаріле питання.
Роберт Фішер,

Відповіді:


146

Java 8 (випущений 18 березня 2014 р.) Підтримує каррінг. Приклад коду Java, опублікованого у відповіді від missingfaktor, можна переписати як:

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.


11
Я вражений Java 8, але Clojure - це переконлива платформа, яка перевищує функціональні можливості мови. Clojure пропонує високоефективні, незмінні структури даних та складні технології одночасності, такі як операційна пам'ять програмного забезпечення.
Михайло Пасха

2
Дякую за рішення, не стільки за мовне копання :) Більш слабка підтримка IDE - це проблема, але інструментарій / бібліотеки не є чітким - повернувшись до Java 8 після Clojure, мені дійсно бракує інструментів midje, таких бібліотек, як core .async та мовні функції, такі як макроси та простий функціональний синтаксис. Приклад каррі в clojure:(def adder5 (partial + 5)) (prn (adder5 4)) (prn adder5 6)
Корні

5
Clojure може бути чудовою мовою, але проблема в тому, що вона просто занадто «чужа» для більшості розробників Java, які звикли лише до звичайного синтаксису в стилі C; дуже важко побачити значну міграцію на Clojure (або будь-яку іншу альтернативну мову JVM) у майбутньому, особливо враховуючи, що кілька таких мов існують вже багато років, і цього не сталося (те саме явище трапляється у світі .NET, де такі мови, як F #, залишаються маргінальними).
Rogério

2
Я повинен проголосувати проти, тому що це свідчить простий випадок. Спробуйте той, який із String створює власний клас, який потім перетворюється на якийсь інший клас, і порівняйте кількість коду
M4ks

11
@ M4ks Питання полягає лише в тому, чи підтримує Java каррінг чи ні, справа не в кількості коду в порівнянні з іншими мовами.
Роджеріо

67

Каррінг і часткове застосування абсолютно можливе в 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

@OP: Обидва - це виконувані фрагменти коду, і ви можете спробувати їх на ideone.com.
missingfaktor

16
Ця відповідь застаріла з моменту випуску Java 8. Дивіться відповідь Роджеріо для більш стислого способу.
Маттіас Браун,

15

Є багато варіантів для каррінгу за допомогою Java 8. Функція типу Javaslang і jOOλ, обидва пропонують каррінг з коробки (я думаю, це було недоглядання в JDK), а модуль Cyclops Functions має набір статичних методів для каррінгу функцій 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


IMO, це те, що справді називається curryingу Curry.currynвихідному коді.
Лебекка

13

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

Стара відповідь:

Java не найкращий вибір, якщо ви збираєтеся використовувати технології функціонального програмування. Як писав missingfaktor, вам доведеться написати досить велику кількість коду, щоб досягти бажаного.

З іншого боку, ви не обмежені Java на JVM - ви можете використовувати Scala або Clojure, які є функціональними мовами (Scala насправді є функціональною і OO).


8

Для каррінгу потрібно повернути функцію . Це неможливо з 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

1
Тепер, коли я закінчив свій приклад (розроблений з нуля), виявляється, що єдиною різницею у відповіді на відсутні коди є те, що я не використовую анонімні класи;)
Андреас Долк,

1
@missingfaktor - mea culpa;)
Андреас Долк,

5

Ну, 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 це нестандартно ;-)


5

Можна емулювати каррінг за допомогою 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

Так, перегляньте приклад коду самі:

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

Більше того, пізніше ви зможете побачити, як ви можете використовувати його в стилі JS як

curriedAdd.apply(1).apply(2) //in Java
//is equivalent to 
curriedAdd(1)(2) // in JS

4

Ще один приклад щодо можливостей 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;

3

Каррінг методу завжди можливий на Java, але він не підтримує його стандартним способом. Спроба досягти цього є складною і робить код досить нечитабельним. Java не є відповідною мовою для цього.


3

Інший вибір тут для 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();

2

Хоча ви можете робити каррінг на Java, це потворно (тому що це не підтримується). У Java простіше і швидше використовувати прості цикли та прості вирази. Якщо ви опублікуєте приклад того, де ви б використовували каррі, ми можемо запропонувати альтернативи, які роблять те саме.


3
Яке відношення каррі має до петель ?! Принаймні знайдіть цей термін, перш ніж відповісти на запитання щодо нього.
missingfaktor

@missingFaktor, до колекцій, як правило, застосовуються функції з функцією "curried". наприклад list2 = list.apply (curriedFunction), де curriedFunction може бути 2 * ?В Java ви зробите це за допомогою циклу.
Пітер Лорі

@Peter: Це часткове застосування, а не каррі. І жодне не є специфічним для операцій збору.
missingfaktor

@missingfaktor, Моя думка: щоб не зациклюватися на певній функції, а зробити крок назад і поглянути на ширшу проблему, і дуже ймовірно, що буде просте рішення.
Пітер Лорі

@Peter: Якщо ви хочете поставити під сумнів питання, вам слід розмістити своє зауваження як коментар, а не як відповідь. (ІМХО)
відсутній фактор

2

Це бібліотека для каррінгу та часткового застосування на Java:

https://github.com/Ahmed-Adel-Ismail/J-Curry

Він також підтримує деструктуризацію Tuples і Map.Entry в параметри методу, наприклад, передачу Map.Entry в метод, який приймає 2 параметри, тому Entry.getKey () перейде до першого параметра, а Entry.getValue () піде на другий параметр

Детальніше у файлі README


2

Перевага використання Currying в Java 8 полягає в тому, що він дозволяє вам визначати функції вищого порядку, а потім передавати функцію першого порядку та аргументи функції в ланцюговому, елегантному вигляді.

Ось приклад для числення, похідної функції.

  1. Давайте визначимо наближення похідної функції як (f (x + h) -f (x)) / h . Це буде функція вищого порядку
  2. Обчислимо похідну 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
      }
    }

0

Так, я погоджуюсь з @ Jérôme, викривлення в 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");
  }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.