我想知道是否有任何方法可以在 Java 中提取它。我认为如果没有对闭包的本机支持,这是不可能的。
17 回答
Java 8(2014 年 3 月 18 日发布)确实支持柯里化。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 中,Currying 和部分应用程序是绝对可行的,但所需的代码量可能会让你望而却步。
一些代码来演示 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
使用 Java 8 进行 Currying 有很多选项。函数类型 Javaslang 和 jOOλ 都提供开箱即用的 Currying(我认为这是 JDK 中的疏忽),并且Cyclops Functions 模块具有一组用于 Currying JDK Functions 的静态方法和方法参考。例如
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);
编辑:截至 2014 年和 Java 8,Java 中的函数式编程现在不仅可能,而且不丑(我敢说漂亮)。参见例如Rogerio 的回答。
老答案:
如果您打算使用函数式编程技术,Java 不是最佳选择。正如missingfaktor 所写,您将不得不编写大量代码来实现您想要的。
另一方面,您不仅限于 JVM 上的 Java - 您可以使用Scala或Clojure,它们是函数式语言(Scala 实际上既是函数式语言又是 OO 语言)。
柯里化需要返回一个函数。这对于 java 是不可能的(没有函数指针),但我们可以定义并返回一个包含函数方法的类型:
public interface Function<X,Z> { // intention: f(X) -> Z
public Z f(X x);
}
现在让我们进行一个简单的除法。我们需要一个Divider:
// 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
可以使用 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
}
}
是的,请自己查看代码示例:
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中的参数的部分应用, curried本身就是一个函数。现在,当我们在屏幕上打印它时,它现在完全应用了。
此外,稍后您可以看到如何以 JS 风格使用它
curriedAdd.apply(1).apply(2) //in Java
//is equivalent to
curriedAdd(1)(2) // in JS
好吧,Scala、Clojure 或 Haskell(或任何其他函数式编程语言……)绝对是用于柯里化和其他函数式技巧的语言。
话虽如此,在没有大量样板文件的情况下当然可以使用 Java 进行 curry(好吧,尽管必须明确说明类型会带来很多伤害 - 只需看一下curried
示例 ;-))。
下面的测试展示了这两种情况,将aFunction3
变成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);
}
以及partial application,尽管在此示例中它并不是真正的类型安全:
@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 应用程序示例仍然是一个问题,并且 currying 示例需要jcurry中的大量样板代码,但它是可行的。
总而言之,它是可行的,但在 Scala 中它是开箱即用的 ;-)
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;
在 Java 中,对方法进行柯里化总是可能的,但它不以标准方式支持它。试图实现这一点很复杂,并且使代码非常难以阅读。Java 不是合适的语言。
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();
虽然您可以在 Java 中进行 Currying,但它很丑陋(因为它不受支持)。在 Java 中,使用普通循环和简单表达式更简单、更快捷。如果您发布一个使用柯里化的示例,我们可以建议做同样事情的替代方案。
在 Java 8 中使用 Currying 的优点是它允许您定义高阶函数,然后以链式、优雅的方式传递一阶函数和函数参数。
这是微积分的一个例子,导数函数。
- 让我们将导函数近似定义为(f(x+h)-f(x))/h。这将是高阶函数
- 让我们计算 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
}
}
这是一个用于在 Java 中进行柯里化和部分应用程序的库:
https://github.com/Ahmed-Adel-Ismail/J-Curry
它还支持将元组和 Map.Entry 解构为方法参数,例如将 Map.Entry 传递给采用 2 个参数的方法,因此 Entry.getKey() 将转到第一个参数,而 Entry.getValue()将使用第二个参数
README 文件中的更多详细信息
虽然所有其他答案都集中在特定示例上,但我仍然想提供一个通用解决方案来将二进制函数转换为柯里化函数。
private static <A, B, C> Function<A, Function<B, C>> Curry(BiFunction<A, B, C> f) {
return a -> b -> f.apply(a, b);
}
是的,我同意@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");
}
}
// 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));
}
}