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.
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.
Câu trả lời:
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.
(def adder5 (partial + 5)) (prn (adder5 4)) (prn adder5 6)
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
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);
currying
trong Curry.curryn
mã nguồn.
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).
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
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 curried
ví dụ ;-)).
Các thử nghiệm rống lên giới thiệu cả hai, tách lạng bộ một Function3
thà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 ;-)
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
}
}
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
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;
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.
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();
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ự.
2 * ?
Trong Java, bạn sẽ thực hiện việc này bằng một vòng lặp.
Đâ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
Ư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.
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
}
}
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");
}
}