复合函数
函数像积木一样,复合为其他函数。
例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
。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/180304.html