文章目录
- Java中实现柯里化
- 需求
- 实现
- 完整代码和结果
Java中实现柯里化
本文实现仅作讨论,不考虑性能等其他因素
本文提到的 函数 均为 静态方法 的代指
需求
假设有函数 int sum(int a, int b, int c, int d)
它能对四个整数求和。
希望 sum(1, 2, 3, 4)
能写为 sum(1, 2)(3)(4)
,这种形式的转变叫 柯里化。
那么显然 sum
函数的返回值 或者 是一个计算了一半的函数,或者 是计算结果。
而 Java 并不支持这种联合类型(即,或者A类或者B类),所以我们需要设计一个元组,一个 record
类,作为柯里化函数的返回值类型,形如(计算了一半的函数, 计算结果)
。当计算完成时前者为 null
后者非空,反之亦然。
实现
为了简单起见,我们不使用固定长度的参数列表,而是使用一个 List
作为 需要被柯里化的函数 的唯一参数。
所以 sum
的函数签名如下:
static int sumOfFourNums(List<Integer> nums) {
return nums.stream().mapToInt(Integer::intValue).sum();
}
之所以使用 List
而不是数组的原因会在稍后说明。
现在我们新建一个 FP 类,并在其中定义作为函数返回值的 record
,以及 record
中第一个值、也就是 计算到一半的函数 的接口。
class FP<T1, T2> {
static record FnResult<T1, T2>(
CurriedFn<T1, T2> fn,
T2 value
) {}
interface CurriedFn<T1, T2> extends Function<List<T1>, FnResult<T1, T2>> {}
然后我们定义 柯里化函数,它是一个 装饰器,所以它应该需求一个函数并返回 一个柯里化的/装饰的 函数。但这还不够,因为我们还需要知道 被装饰的函数 的参数列表的长度,所以再需求一个长度参数。
CurriedFn<T1, T2> currying(Function<List<T1>, T2> fn, int paramc)
如果你足够仔细,会发现
curring
函数不是静态方法。
接下来我们具体的实现它:
- 首先创建参数存储列表
stored
,它将存储那些已经传入但总数尚未满足需求的实参。由于
stored
会在闭包内使用,因此无法重新赋值,也就不能使用数组;
当然也可以创建一个长度为paramc
的数组,效果一样,但那样就需要一个额外的变量来记忆和更新非空元素数量 - 然后定义一个内部函数
inner
它将是我们要返回出去的函数。 - 对于实参列表为空的情况直接报错,这是非法输入
- 计算已经捕获的实参数量,如果它
- > 形参数量:报错
- = 形参数量:计算结果,并返回
(null, 计算结果)
- < 形参数量:返回
(inner, null)
特别要注意,inner被定义在函数外部,作为实例属性,这是因为 inner 内需要返回自身(的名称),而匿名方法无法取的自身,因此采取这种折中办法。
private CurriedFn<T1, T2> inner;
CurriedFn<T1, T2> currying(Function<List<T1>, T2> fn, int paramc) {
List<T1> stored = new ArrayList<>();
inner = args -> {
if (args == null) {
throw new IllegalArgumentException("Argument cannot be null");
}
int argc = args.size() + stored.size();
// 实参过多
if (argc > paramc) {
throw new RuntimeException("实参数量超过形参: " + argc + " > " + paramc + "!");
}
stored.addAll(args);
// 实参数量 = 形参数量,计算结果
if (argc == paramc) {
return new FnResult<>(null, fn.apply(stored));
}
// 实参数量 < 形参数量,继续等待
return new FnResult<>(inner, null);
};
return inner;
}
完整代码和结果
完整代码:
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
public class App {
public static void main(String[] args) {
System.out.print("sum([1, 2, 3, 4]) == sum([1, 2])([3])([4]) == ");
FP<Integer, Integer> fp;
FP.CurriedFn<Integer, Integer> fn;
FP.FnResult<Integer, Integer> result;
fp = new FP<>();
fn = fp.currying(App3::sumOfFourNums, 4);
result = fn
.apply(List.of(1, 2))
.fn()
.apply(List.of(3))
.fn()
.apply(List.of(4));
System.out.println(result.value()); // 10
}
static int sumOfFourNums(List<Integer> nums) {
return nums.stream().mapToInt(Integer::intValue).sum();
}
}
class FP<T1, T2> {
private CurriedFn<T1, T2> inner;
static record FnResult<T1, T2>(
CurriedFn<T1, T2> fn,
T2 value
) {}
interface CurriedFn<T1, T2> extends Function<List<T1>, FnResult<T1, T2>> {}
CurriedFn<T1, T2> currying(Function<List<T1>, T2> fn, int paramc) {
List<T1> stored = new ArrayList<>();
inner = args -> {
if (args == null) {
throw new IllegalArgumentException("Argument cannot be null");
}
int argc = args.size() + stored.size();
// 实参过多
if (argc > paramc) {
throw new RuntimeException("实参数量超过形参: " + argc + " > " + paramc + "!");
}
stored.addAll(args);
// 实参数量 = 形参数量,计算结果
if (argc == paramc) {
return new FnResult<>(null, fn.apply(stored));
}
// 实参数量 < 形参数量,继续等待
return new FnResult<>(inner, null);
};
return inner;
}
}
运行效果:
sum([1, 2, 3, 4]) == sum([1, 2])([3])([4]) == 10