Java复合函数与柯理化函数

复合函数

函数像积木一样,复合为其他函数。
例1,f(x) = x + 2 并且 g(x) = x * 2,可得:f · g(x) = f round g = f(g(x)) = f(x * 2) = (x * 2) + 2。

计算顺序

与书写顺序相反,先g,再f。

JDK中的案例

andThen和compose方法。根据上面我们知道,内部函数先调用,因此,这两个方法的区别只是调用顺序不一样。
在这里插入图片描述

使用方法

编写复合函数逻辑:

    static <T, U, V> Function<V, U> compose(Function<T, U> f, Function<V, T> g) {
        return x -> f.apply(g.apply(x));
    }

使用复合函数,编写测试代码:

    // 奇怪?为什么不把上面的组合逻辑也写为变量形式?注意,这是因为泛型只能定义在接口、类、方法。
    public static final Function triple = arg -> arg * 3;

    public static final Function square = arg -> arg * arg;

    public static void main(String[] args) {
        assertEquals(27, compose(triple, square).apply(3));
    }
高级玩法

定义组合逻辑:

    static <T, U, V> Function<Function<U, V>, Function<Function<T, U>, Function<T, V>>> higherCompose() {
        return f -> g -> x -> f.apply(g.apply(x));
    }

调用测试:

    public void test() {
        assertEquals(Integer.valueOf(12), Function.<Integer, Integer, Integer>higherCompose().apply(square).apply(triple).apply(2));
    }

懵了?不要晕,类型说明:

    static <T, U, V> Function<Function<U, V>, Function<Function<T, U>, Function<T, V>>> higherCompose() {
        // f => Function<U, V>
        // g => Function<T, U>
        // x => T
        return (Function<U, V> f)
                -> (Function<T, U> g)
                -> (T x)
                -> f.apply(g.apply(x));     // 最终 f、g、x 的调用关系在此决定
    }
    
    // 更进一步反简化:
    static <T, U, V> Function<Function<U, V>, Function<Function<T, U>, Function<T, V>>> higherCompose() {
        return (Function<U, V> f) -> {
            return (Function<T, U> g) -> {
                return (T x) -> {
                    return f.apply(g.apply(x));
                };
            };
        };
    }

柯理化函数

我们称函数 f(x) (y) 是 f(x, y) 的柯理化形式。

例1,有普通函数:函数名(参数1, 参数2, ...) ,柯理化后:函数名(参数1)(参数2)...

综上

一个多参函数应用这种转换就成为柯理化(currying)。

Java中使用柯理化函数(多参函数)

我们观察Java java.util.function.Function 接口,该接口apply方法似乎是一个参数?
在这里插入图片描述
解决方法,定义一个Tuple对象用于传参:

public class Tuple<T, U> {

    public final T _1;
    public final U _2;

    public Tuple(T t, U u) {
        this._1 = Objects.requireNonNull(t);
        this._2 = Objects.requireNonNull(u);
    }

    @Override
    public String toString() {
        return String.format("(%s,%s)", _1, _2);
    }
}

定义柯理化函数:

    public static <A, B, C> Function<A, Function<B, C>> curry(Function<Tuple<A, B>, C> f) {
        return a -> b -> f.apply(new Tuple<>(a, b));
    }

实现柯理化函数,并测试:

	// 1.先定义多参函数的实现:
	private Function<Tuple<Integer, Double>, Double> f = x -> x._1 * (1 + x._2 / 100);
	// 2.将多参函数柯理化:
	private Function<Integer, Function<Double, Double>> g = curry(f);
	
	// 3.调用柯理化函数,测试计算结果:
    @Test
    public void testCurry() {
    	// 柯理化函数调用:
    	assertEquals(95.23, g.apply(89).apply(7.0));
    	// 柯理化函数等价多参函数:
    	assertEquals(95.23, f.apply(new Tuple<>(89, 7.0));
    }
柯理化函数的部分应用

例如,上面测试用例中的完整应用g.apply(89).apply(7.0),我们只调用g.apply(89)时返回的是一个Function<Double, Double>,需要我们再后面追加调用.apply(7.0)才能完成柯理化函数的完整调用,并计算返回结果。

综上,我们可以得出,柯理化函数部分应用时陪域不是一个值,而是一个函数集。当柯理化函数完整调用时,返回结果才是一个数字集

柯理化优点

从原理上:函数完全变成接受一个参数,返回一个值的固定形式。简化了数式编程的复杂性。
从代码上:更加简洁,忽略不必要的信息。

实现原理

有了上面两部的分析,我们就知道在Java中怎么实现我们的柯理化函数了。

反过来看我们刚才是怎么定义柯理化函数的:

    public static <A, B, C> Function<A, Function<B, C>> curry(Function<Tuple<A, B>, C> f) {
        return a -> b -> f.apply(new Tuple<>(a, b));
    }

其实就是柯理化函数应用(apply)时,就传一个参数,返回值是Function。直到参数全部传完我们就构建一个Tuple对象,用于包裹所有参数,并调用最终的求值逻辑计算结果返回。

Java与JavaScript中的柯理化函数

JavaScript:

// 定义柯理化函数
function add(a) {
    return function(b) {
        return a + b;
    }
}

// 调用柯理化函数
add(1)(2);

将JavaScript的add(1)(2)调用方式如果换成Java,等价于:add.apply(1).apply(2)

补充:Haskell语言调用:add 1 2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值