【问题标题】:Does Java support Currying?Java 支持柯里化吗?
【发布时间】:2011-05-26 05:54:48
【问题描述】:

我想知道是否有任何方法可以在 Java 中提取它。我认为没有对闭包的原生支持是不可能的。

【问题讨论】:

  • 作为记录,Java 8 现在支持柯里化和部分应用程序,并且原生支持闭包。这是一个非常过时的问题。

标签: java functional-programming currying partial-application


【解决方案1】:

Java 8(2014 年 3 月 18 日发布)确实支持柯里化。 the answer by 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 可用的情况下,我认为没有理由使用 Scala 或 Clojure 等替代 JVM 语言。当然,它们提供了其他语言功能,但这不足以证明过渡成本和 IMO 较弱的 IDE/工具/库支持是合理的。

【讨论】:

  • Java 8 给我留下了深刻的印象,但 Clojure 是一个超越函数式语言特性的引人注目的平台。 Clojure 提供高效、不可变的数据结构和复杂的并发技术,例如软件事务内存。
  • 感谢您的解决方案,而不是语言挖掘 :) 较弱的 IDE 支持是一个问题,但工具/库并不明确 - 在 Clojure 之后又回到 Java 8,我确实缺少 midje 工具、core.async 等库以及宏和简单的函数式语法等语言特性。 clojure 中的柯里化示例:(def adder5 (partial + 5)) (prn (adder5 4)) (prn adder5 6)
  • Clojure 可能是一门很棒的语言,但问题是它对于大多数只习惯于传统 C 样式语法的 Java 开发人员来说太“陌生”了;未来很难看到向 Clojure(或任何其他替代 JVM 语言)的重大迁移,特别是考虑到几种这样的语言已经存在多年并且没有发生(同样的现象发生在 .NET 世界中,像 F# 这样的语言仍然处于边缘地位)。
  • 我不得不投反对票,因为它显示了简单的案例。尝试从 String 中创建自己的类,然后转换为其他类,然后比较代码量
  • @M4ks 问题只是Java是否支持柯里化,而不是与其他语言相比的代码量。
【解决方案2】:

Currying 和部分应用程序在 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 是上面 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:两者都是可执行代码 sn-ps,您可以在 ideone.com 上试用。
  • 自 Java 8 发布以来,此答案已过时。有关更简洁的方法,请参阅 Rogério 的答案。
【解决方案3】:

Java 8 有很多用于 Currying 的选项。函数类型 Javaslang 和 jOOλ 都提供开箱即用的 Currying(我认为这是 JDK 中的一个疏忽),CyclopsFunctions module 有一组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;
 }

“Currying”也可供消费者使用。例如,要返回一个具有 3 个参数的方法,其中 2 个已经应用,我们会做类似的事情

 return CurryConsumer.curryC3(this::methodForSideEffects).apply(2).apply(2);

Javadoc

【讨论】:

  • IMO,这就是Curry.curryn 源代码中真正所谓的currying
【解决方案4】:

编辑:截至 2014 年和 Java 8,Java 中的函数式编程现在不仅可以实现,而且不丑(我敢说漂亮)。参见例如Rogerio's answer

旧答案:

如果您打算使用函数式编程技术,Java 不是最佳选择。正如missingfaktor 所写,您将不得不编写大量代码来实现您想要的。

另一方面,您不仅限于 JVM 上的 Java - 您可以使用 ScalaClojure,它们是函数式语言(事实上,Scala 既是函数式语言又是 OO)。

【讨论】:

    【解决方案5】:

    柯里化需要返回一个函数。这在 java 中是不可能的(没有函数指针),但我们可以定义并返回一个包含函数方法的类型:

    public interface Function<X,Z> {  // intention: f(X) -> Z
       public Z f(X x);
    }
    

    现在让我们curry一个简单的除法。我们需要一个分隔符

    // 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
    

    【讨论】:

    • 现在我已经完成了我的示例(从头开始开发),事实证明,与 missingcodes 答案的唯一区别是我不使用匿名类;)
    • @missingfaktor - 过失;)
    【解决方案6】:

    可以使用 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
        }
    }
    

    【讨论】:

      【解决方案7】:

      是的,请自行查看代码示例:

      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
      

      【讨论】:

        【解决方案8】:

        嗯,Scala、Clojure 或 Haskell(或任何其他函数式编程语言...)绝对是用于柯里化和其他函数式技巧的语言。 p>

        话虽如此,在没有大量样板文件的情况下使用 Java 进行 curry 肯定是可能的(不过,必须明确说明类型会造成很大的伤害 - 只需看看 curried 示例 ;-) )。

        下面的测试展示了两者,curryingFunction3 转换为Function1 =&gt; Function1 =&gt; 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,相对容易,虽然“真正的类型安全”对于部分应用程序示例仍然是一个问题,并且 currying 示例需要在 jcurry 中使用大量的样板代码strong>,但它是可行的。

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

        【讨论】:

          【解决方案9】:

          更多关于 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;
          

          【讨论】:

            【解决方案10】:

            在 Java 中总是可以对方法进行柯里化,但它不以标准方式支持它。试图实现这一点很复杂,并且使代码非常难以阅读。 Java 不是合适的语言。

            【讨论】:

              【解决方案11】:

              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();
              

              【讨论】:

                【解决方案12】:

                虽然您可以在 Java 中进行 Currying,但它很难看(因为它不受支持)在 Java 中,使用普通循环和简单表达式更简单、更快捷。如果您发布一个使用柯里化的示例,我们可以建议做同样事情的替代方案。

                【讨论】:

                • 柯里化和循环有什么关系?!至少在回答有关它的问题之前查一下这个词。
                • @missingFaktor,柯里化函数通常应用于集合。例如list2 = list.apply(curriedFunction) 其中 curriedFunction 可能是 2 * ? 在 Java 中,您可以使用循环来执行此操作。
                • @Peter:那是部分应用,不是柯里化。两者都不是特定于收集操作的。
                • @missingfaktor,我的意思是;不要拘泥于特定的功能,而是退后一步,看看更广泛的问题,很可能会有一个简单的解决方案。
                • @Peter:如果您想质疑问题的重点,您应该将您的评论作为评论发布,而不是作为答案。 (恕我直言)
                【解决方案13】:

                这是一个用于在 Java 中进行柯里化和部分应用程序的库:

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

                它还支持将 Tuples 和 Map.Entry 解构为方法参数,例如将 Map.Entry 传递给采用 2 个参数的方法,因此 Entry.getKey() 将转到第一个参数,然后是 Entry。 getValue() 将用于第二个参数

                README 文件中的更多详细信息

                【讨论】:

                  【解决方案14】:

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

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

                  1. 让我们将导数函数近似定义为 (f(x+h)-f(x))/h。这将是高阶函数
                  2. 让我们计算 2 个不同函数的导数,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
                        }
                      }
                  

                  【讨论】:

                    【解决方案15】:

                    是的,我同意 @Jérôme 的观点,Java 8 中的 curring 不支持像 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");
                      }
                    }
                    

                    【讨论】:

                      【解决方案16】:

                      虽然所有其他答案都集中在特定示例上,但我仍然想提供一个通用解决方案,将二进制函数转换为柯里化函数。

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

                      【讨论】:

                        【解决方案17】:
                        // Usage of `BiFnCurry.curry` defined below
                        someStream.map(curry(this::someBiFunction).apply(1stBiFnParam))
                        // someBiFunction is Function<1stBiFnParam, 2ndBiFnParam, R>
                        // curry(this::someBiFunction).apply(1stBiFnParam) returns Function<2ndBiFnParam, R>
                        
                        public class BiFnCurry {
                            public static <T1, T2, R> Function<T1, Function<T2, R>> curry(BiFunction<T1, T2, R> biFn) {
                                return t1 -> (t2 -> biFn.apply(t1, t2));
                            }
                        }
                        

                        【讨论】:

                          猜你喜欢
                          • 1970-01-01
                          • 2022-11-22
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 2011-05-26
                          • 2016-01-01
                          • 2011-08-18
                          相关资源
                          最近更新 更多