Java中实现柯里化

文章目录

  • 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 函数不是静态方法。

接下来我们具体的实现它:

  1. 首先创建参数存储列表 stored,它将存储那些已经传入但总数尚未满足需求的实参。

    由于 stored 会在闭包内使用,因此无法重新赋值,也就不能使用数组;
    当然也可以创建一个长度为 paramc 的数组,效果一样,但那样就需要一个额外的变量来记忆和更新非空元素数量

  2. 然后定义一个内部函数 inner 它将是我们要返回出去的函数。
  3. 对于实参列表为空的情况直接报错,这是非法输入
  4. 计算已经捕获的实参数量,如果它
    • > 形参数量:报错
    • = 形参数量:计算结果,并返回 (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
  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值