Java是否支持Currying?


Answers:


145

Java 8(2014年3月18日发布)不支持curring。可以将Missingfaktor答案中发布的示例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 /工具/库支持IMO的不足。


11
Java 8给我留下了深刻的印象,但是Clojure是一个功能语言功能之外的引人注目的平台。Clojure提供了高效,不变的数据结构和复杂的并发技术,例如软件事务存储。
迈克尔·复活节复活节

2
感谢您提供的解决方案,而不是语言挖掘方面的问题:)较弱的IDE支持是一个问题,但是工具/库不是很明确-Clojure之后回到Java 8,我真的缺少工具midje,像核心这样的库.async和语言功能(例如宏和简单的函数语法)。(def adder5 (partial + 5)) (prn (adder5 4)) (prn adder5 6)
Clojure中的易变

5
Clojure也许是一门伟大的语言,但是问题在于,对于大多数习惯于使用常规C样式语法的Java开发人员来说,它太“陌生”了。很难看到将来会大量迁移到Clojure(或任何其他替代JVM语言),特别是考虑到已经有很多种这样的语言已经存在很多年了,而且还没有发生(在.NET世界中发生了相同的现象,像F#这样的语言仍处于边缘)。
罗杰里奥

2
我不得不投票,因为它显示了简单的情况。尝试使用String编写的一个类来创建您自己的类,然后将该类转换为其他类,并比较代码量
M4ks

11
@ M4ks问题仅在于Java是否支持currying,与其他语言相比,它与代码量无关。
罗杰里奥

67

在Java中绝对可以使用Curry和部分应用程序,但是所需的代码量可能会使您无法使用。


一些代码来演示Java中的currying和部分应用程序:

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这是上述Java代码的Haskell等效项:

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 2011年

16
自Java 8发行以来,这个答案已经过时了。有关更简洁的方法,请参见Rogério的答案。
Matthias Braun 2014年

15

使用Java 8进行Currying的选项很多。函数类型Javaslang和jOOλ都提供了Currying的功能(我认为这是JDK的一个疏漏),并且Cyclops Functions模块具有一组用于Currying 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);

Java文档


IMO,这是真正curryingCurry.curryn源代码中所说的。
莱贝卡

13

编辑:从2014年和Java 8开始,Java中的功能编程现在不仅可行,而且也不难看(我敢说漂亮)。例如,参见Rogerio的答案

旧答案:

如果您要使用函数式编程技术,那么Java不是最佳选择。正如missingfaktor所写,您将必须编写大量代码才能实现所需的功能。

另一方面,您不仅限于在JVM上使用Java,还可以使用功能性语言ScalaClojure(实际上,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
现在,我已经完成了示例(从头开始开发),事实证明,与漏码答案的唯一区别是我不使用匿名类;)
Andreas Dolk 2011年

1
@missingfaktor-mea culpa;)
Andreas Dolk

5

那么,斯卡拉,Clojure的或哈斯克尔(或任何其他函数式编程语言...)肯定是语言要使用柯里和其他功能的技巧。

这么说当然有可能用Java来实现而无需人们可能期望的大量样板程序(当然,必须明确说明类型会带来很多伤害-只看curried示例;-)。

下面的测试展示了两者, 讨好一个Function3Function1 => 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);
}

取自我刚刚在JavaOne明天明天就花了一个小时才“有趣,因为我很无聊”的概念证明; ;-)代码可在这里获得:https : //github.com/ktoso/jcurry

一般的想法可以相对容易地扩展到FunctionN => FunctionM,尽管“真实类型安全性”对于partia应用程序示例仍然是一个问题,而当前的示例在jcurry中将需要大量的样板代码,但这是可行的。

总而言之,这是可行的,但是在Scala中,它是开箱即用的;-)


5

可以使用Java 7 MethodHandles来模拟currying: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函数,它返回另一个函数,并且可以用于存储在其中的参数的部分应用咖喱这本身就是一个函数。现在,当我们在屏幕上打印时,此功能将在以后完全应用。

此外,稍后您将看到如何以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中进行Currying,但是它很丑陋(因为它不受支持)。在Java中,使用纯循环和简单表达式会更简单,更快捷。如果您发布了一个示例,该示例将在何处使用curring,我们可以建议您做同样的事情。


3
currying与循环有什么关系?至少在回答有关问题之前先查询一下该术语。
missingfaktor 2011年

@missingFaktor,通常将咖喱函数应用于集合。例如list2 = list.apply(curriedFunction)其中curriedFunction可能2 * ?在Java中,您可以使用循环来完成此操作。
彼得·劳瑞

@Peter:这是部分应用,不是可循的。而且两者都不是特定于收集操作的。
missingfaktor 2011年

@missingfaktor,我的意思是;不想挂在特定功能上,而是退后一步,看看更广泛的问题,很可能会有一个简单的解决方案。
彼得·劳瑞

@Peter:如果您想质疑问题的重点,则应将您的评论作为评论而不是作为答案发表。(恕我直言)
missingfaktor


2

在Java 8中使用Currying的优点在于,它允许您定义高阶函数,然后以链接的优雅方式传递一阶函数和函数参数。

这是微积分的一个例子,微分函数。

  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或其他功能编程语言一样,不以标准方式支持curr。

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");
  }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.