Java có hỗ trợ Currying không?


88

Tôi đã tự hỏi nếu có bất kỳ cách nào để kéo điều đó trong Java. Tôi nghĩ rằng không thể thực hiện được nếu không có hỗ trợ bản địa cho việc đóng cửa.


4
Đối với hồ sơ, Java 8 hiện hỗ trợ ứng dụng currying và một phần và có hỗ trợ gốc cho các đóng. Đây là một câu hỏi cực kỳ lỗi thời.
Robert Fischer

Câu trả lời:


145

Java 8 (phát hành ngày 18 tháng 3 năm 2014) không hỗ trợ currying. Mã Java mẫu được đăng trong câu trả lời bởi missfaktor có thể được viết lại như sau:

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

... khá tốt. Cá nhân tôi, với Java 8 có sẵn, tôi thấy ít lý do để sử dụng một ngôn ngữ JVM thay thế như Scala hoặc Clojure. Tất nhiên, họ cung cấp các tính năng ngôn ngữ khác, nhưng điều đó không đủ để biện minh cho chi phí chuyển đổi và hỗ trợ IDE / công cụ / thư viện yếu hơn, IMO.


11
Tôi ấn tượng với Java 8, nhưng Clojure là một nền tảng hấp dẫn ngoài các tính năng của ngôn ngữ chức năng. Clojure cung cấp cấu trúc dữ liệu bất biến, hiệu quả cao và các kỹ thuật đồng thời phức tạp như bộ nhớ giao dịch phần mềm.
Michael Easter vào

2
Cảm ơn vì giải pháp, không phải quá nhiều cho việc đào ngôn ngữ :) Hỗ trợ IDE yếu hơn là một vấn đề, nhưng công cụ / thư viện không rõ ràng - đã quay trở lại Java 8 sau Clojure, tôi thực sự thiếu các công cụ midje, các thư viện như lõi .async và các tính năng ngôn ngữ như macro và cú pháp chức năng dễ dàng. Ví dụ về cà ri trong (def adder5 (partial + 5)) (prn (adder5 4)) (prn adder5 6)
áo choàng

5
Clojure có thể là một ngôn ngữ tuyệt vời, nhưng vấn đề là nó quá "xa lạ" đối với phần lớn các nhà phát triển Java vốn chỉ quen với cú pháp kiểu C thông thường; rất khó để thấy một sự chuyển đổi đáng kể sang Clojure (hoặc bất kỳ ngôn ngữ JVM thay thế nào khác) trong tương lai, đặc biệt khi xem xét rằng một số ngôn ngữ như vậy đã tồn tại trong nhiều năm và nó đã không xảy ra (hiện tượng tương tự cũng xảy ra trong thế giới .NET, trong đó các ngôn ngữ như F # vẫn còn ngoài lề).
Rogério

2
Tôi phải từ chối, vì nó chỉ ra trường hợp đơn giản. Hãy thử một trong đó từ String làm cho lớp của riêng bạn mà sau đó chuyển thành một số lớp khác, và so sánh số lượng mã
M4ks

11
@ M4ks Câu hỏi chỉ là Java có hỗ trợ currying hay không, nó không phải về số lượng mã khi so sánh với các ngôn ngữ khác.
Rogério

67

Hoàn toàn có thể sử dụng ứng dụng một phần và lập trình trong Java, nhưng số lượng mã cần thiết có thể sẽ khiến bạn tắt.


Một số mã để chứng minh ứng dụng currying và một phần trong 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 ở đây là Haskell tương đương với mã Java trên:

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: Cả hai đều là các đoạn mã thực thi và bạn có thể dùng thử chúng tại ideone.com.
missfaktor

16
Câu trả lời này đã lỗi thời kể từ khi phát hành Java 8. Hãy xem câu trả lời của Rogério để biết một cách ngắn gọn hơn.
Matthias Braun

15

Có rất nhiều tùy chọn để Currying với Java 8. Loại hàm Javaslang và jOOλ đều cung cấp Currying out of the box (tôi nghĩ đây là một sơ suất trong JDK) và mô-đun Cyclops Functions có một tập hợp các phương thức tĩnh cho Currying JDK Functions và các tham chiếu phương pháp. Ví dụ

  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;
 }

'Cà ri' cũng có sẵn cho Người tiêu dùng. Ví dụ: để trả về một phương thức có 3 tham số và 2 trong số những tham số đã được áp dụng, chúng tôi làm điều gì đó tương tự như thế này

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

Javadoc


IMO, đây là những gì thực sự được gọi curryingtrong Curry.currynmã nguồn.
Lebecca

13

CHỈNH SỬA : Kể từ năm 2014 và Java 8, lập trình hàm trong Java giờ đây không chỉ khả thi mà còn không hề xấu (tôi dám nói là đẹp). Xem ví dụ câu trả lời của Rogerio .

Câu trả lời cũ:

Java không phải là lựa chọn tốt nhất, nếu bạn định sử dụng các kỹ thuật lập trình chức năng. Như missfaktor đã viết, bạn sẽ phải viết một lượng mã khá lớn để đạt được những gì bạn muốn.

Mặt khác, bạn không bị giới hạn đối với Java trên JVM - bạn có thể sử dụng Scala hoặc Clojure là các ngôn ngữ chức năng (trên thực tế, Scala là cả chức năng và OO).


8

Currying yêu cầu trả về một hàm . Điều này không thể xảy ra với java (không có con trỏ hàm) nhưng chúng ta có thể xác định và trả về một kiểu có chứa phương thức hàm:

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

Bây giờ chúng ta hãy làm một phép chia đơn giản. Chúng tôi cần một dải phân cách :

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

và một SplitFunction :

// f(x) -> g
public class DivideFunction implements Function<Double, Function<Double, Double>> {
  @Override
  public function<Double, Double> f(Double x) {
    return new Divider(x);
  }

Bây giờ chúng ta có thể thực hiện một phân chia cà ri:

DivideFunction divide = new DivideFunction();
double result = divide.f(2.).f(1.);  // calculates f(1,2) = 0.5

1
Bây giờ tôi đã hoàn thành ví dụ của tôi (được phát triển từ đầu) nó quay ra, rằng sự khác biệt duy nhất missingcodes câu trả lời là tôi không sử dụng các lớp học vô danh;)
Andreas Dolk

1
@missingfaktor - Mea culpa;)
Andreas Dolk

5

Chà, Scala , Clojure hoặc Haskell (hoặc bất kỳ ngôn ngữ lập trình chức năng nào khác ...) chắc chắn là những ngôn ngữ được sử dụng để làm cà ri và các thủ thuật chức năng khác.

Có điều đã nói chắc chắn là có thể sử dụng Java mà không cần quá nhiều bản soạn sẵn mà người ta có thể mong đợi (tốt, việc phải nói rõ ràng về các loại sẽ gây tổn hại rất nhiều - chỉ cần xem curriedví dụ ;-)).

Các thử nghiệm rống lên giới thiệu cả hai, tách lạng bộ một Function3thành 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);
}

cũng như một phần ứng dụng , mặc dù nó không thực sự an toàn trong ví dụ này:

@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);
}

Điều này được lấy từ Proof Of Concept mà tôi vừa triển khai cho vui trước JavaOne vào ngày mai trong một giờ nữa "bởi vì tôi đã chán" ;-) Mã có sẵn ở đây: https://github.com/ktoso/jcurry

Ý tưởng chung có thể được mở rộng thành FunctionN => FunctionM, tương đối dễ dàng, mặc dù "an toàn kiểu thực" vẫn còn là một vấn đề đối với ví dụ ứng dụng partia và ví dụ currying sẽ cần rất nhiều mã soạn sẵn trong jcurry , nhưng nó có thể làm được.

Nói chung, nó có thể làm được, nhưng trong Scala, nó không có lợi ;-)


5

Người ta có thể mô phỏng currying với 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

Có, hãy xem ví dụ mã cho chính bạn:

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

    }

}

Đây là ví dụ đơn giản với curriedAdd là một hàm curried trả về một hàm khác và điều này có thể được sử dụng cho ứng dụng một phần của các tham số như được lưu trữ trong curried vốn là một hàm của chính nó. Điều này sau đó được áp dụng đầy đủ khi chúng tôi in nó trên màn hình.

Hơn nữa, sau này, bạn có thể thấy cách bạn có thể sử dụng nó theo kiểu JS như

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

4

Một trong những khả năng khác về 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;

Bạn cũng có thể xác định các phương thức tiện ích như sau:

static <A1, A2, R> Function<A2, R> curry(BiFunction<A1, A2, R> f, A1 a1) {
    return a2 -> f.apply(a1, a2);
}

Điều này cung cấp cho bạn một cú pháp dễ đọc hơn được cho là:

Function<Integer, Integer> increment = curry(add, 1);
assert increment.apply(5) == 6;

3

Việc sử dụng một phương thức luôn có thể thực hiện được trong Java, nhưng nó không hỗ trợ nó theo cách chuẩn. Cố gắng đạt được điều này là phức tạp và làm cho mã khá khó đọc. Java không phải là ngôn ngữ thích hợp cho việc này.


3

Một sự lựa chọn khác dành cho 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);
}

sau đó bạn có thể đạt được cà ri bằng cách này

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

Mặc dù bạn có thể làm Currying trong Java, nhưng nó rất tệ (vì nó không được hỗ trợ) Trong Java, việc sử dụng các vòng lặp thuần túy và các biểu thức đơn giản sẽ đơn giản và nhanh hơn. Nếu bạn đăng một ví dụ về nơi bạn sẽ sử dụng cà ri, chúng tôi có thể đề xuất các lựa chọn thay thế có tác dụng tương tự.


3
Cà ri có liên quan gì đến vòng lặp ?! Ít nhất hãy tra cứu thuật ngữ trước khi bạn trả lời câu hỏi về nó.
missfaktor

@missingFaktor, các hàm curried thường được áp dụng cho các bộ sưu tập. ví dụ list2 = list.apply (curriedFunction) trong đó curriedFunction có thể là 2 * ?Trong Java, bạn sẽ thực hiện việc này bằng một vòng lặp.
Peter Lawrey

@Peter: Đó là ứng dụng một phần, không phải cà ri. Và không phải là cụ thể cho các hoạt động thu thập.
missfaktor

@missingfaktor, quan điểm của tôi là; không quá lo lắng về một tính năng cụ thể mà hãy lùi lại một bước và nhìn vào vấn đề rộng hơn và rất có thể có một giải pháp đơn giản.
Peter Lawrey

@Peter: Nếu bạn muốn đặt câu hỏi về quan điểm của câu hỏi, bạn nên đăng nhận xét của mình dưới dạng nhận xét, chứ không phải dưới dạng câu trả lời. (IMHO)
missfaktor

2

Đây là một thư viện dành cho ứng dụng một phần và cà ri trong Java:

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

Nó cũng hỗ trợ hủy cấu trúc Tuples và Map.Entry thành các tham số của phương thức, chẳng hạn như truyền một Map.Entry đến một phương thức có 2 tham số, vì vậy Entry.getKey () sẽ chuyển đến tham số đầu tiên và Entry.getValue () sẽ đi cho tham số thứ hai

Thêm chi tiết trong tệp README


2

Ưu điểm của việc sử dụng Currying trong Java 8 là nó cho phép bạn xác định các hàm bậc cao và sau đó chuyển một hàm bậc nhất và các đối số hàm theo một cách liên kết, thanh lịch.

Đây là một ví dụ cho Giải tích, hàm đạo hàm.

  1. Cho phép xác định xấp xỉ hàm đạo hàm dưới dạng (f (x + h) -f (x)) / h . Đây sẽ là chức năng đặt hàng cao
  2. Hãy tính đạo hàm của 2 hàm khác nhau, 1 / x , và phân phối gaussian chuẩn hóa

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

Vâng, tôi đồng ý với @ Jérôme, việc uốn cong trong Java 8 không được hỗ trợ theo cách chuẩn như trong Scala hoặc các ngôn ngữ lập trình chức năng khác.

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");
  }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.