【发布时间】:2018-12-26 14:01:31
【问题描述】:
假设我们有一个class Foo<T>,它表示一些操作,它产生一个T 类型的值。我希望能够组合这个类的两个对象,以防一个产生Function<A, B>,第二个产生A 以获得Foo<B>。我可以使用以下静态方法来做到这一点:
public static <T, U> Foo<U> compose(Foo<Function<T, U>> fn, Foo<T> x) {
// the composition is an implementation detail
}
现在我可以轻松地将Foo<Function<A, B>> 类型的fooAtoB 与Foo<A> 类型的fooA 组合起来,如下所示:
compose(fooAtoB, fooA)
不幸的是,在我的情况下,Foo 生成像 Function<A, Function<B, Function<C, D>>> 这样的嵌套函数是很常见的,我希望能够用 Foo<A>、Foo<B> 和 Foo<C> 组合这些函数来获得 @ 987654337@.
我可以通过以下方式做到这一点:
compose(compose(compose(fooAtoBtoCtoD, fooA), fooB), fooC);
这可行,但有很多嵌套,因此可读性受到影响。理想情况下,我想做的是这样的:
fooAtoBtoCtoD.compose(fooA).compose(fooB).compose(fooC)
但据我所知,不可能表达这样的事情,因为无法命名compose的返回类型,因为compose无法知道T是否在Foo<T> 实际上是一个Function<U, V>。
换一种方式很容易做到:
<U> Foo<U> compose(Function<T, U> fn)
但这又不是很好用。
fooC.compose(fooB.compose(fooA.compose(fooAtoBtoCtoD)))
参数是从右到左写的,有很多嵌套。
这让我想到了这个问题:
有没有办法将Foo 的T 参数限制为仅在compose 方法范围内的类型子集,以便它可以利用该限制?
能让我表达以下内容的东西会很棒:
T extends Function<U, V> in the scope of
<U, V> Foo<V> compose(Foo<U> x) {
// values of type T are convertible to Function<U, V>
// in the scope of this method
}
现在我认为没有这样的东西存在,并且T是一个函数的证明必须来自外部调用compose时,所以是这样的:
<U, V> Foo<V> compose(Function<T, Function<U, V>> fn, Foo<U> x) {
// I can use fn#apply to convert a T to a Function<U, V> here
}
在给定恒等函数时工作正常:
fooAtoBtoCtoD.compose(x -> x, fooA).compose(x -> x, fooB).compose(x -> x, fooC)
但这有点太长了,一遍遍重复x -> x 并不好玩。
解决方案可能是将函数与Foo 一起打包。给定以下类:
class FooWithIdentity<T, U> extends Foo<T> implements Function<U, U> {
@Override
U apply(U val) {
return val;
}
...
}
以及Foo中的以下两种方法:
<P extends Foo<U> & Function<T, Function<U, V>>, U, V> Foo<V> compose(P foo)
{
Function<U, V> fun = foo.apply(value);
return new Foo<>(fun.apply(foo.value));
}
<U> FooWithIdentity<T, U> wrap() {
return new FooWithIdentity<>(this);
}
我实现了以下目标:
fooAtoBtoCtoD.compose(fooA.wrap()).compose(fooB.wrap()).compose(fooC.wrap())
在我看来,这是迄今为止最好的解决方案。
编辑: Radiodef 的解决方案让我想到了一个具有相同界面的更简单解决方案:
class Foo<T>
{
...
<U> U through(Function<Foo<T>, U> fn) {
return fn.apply(this);
}
<U> Function<Foo<Function<T, U>>, Foo<U>> composing() {
return fooFn -> /* compose fooFn and this here */;
}
}
本质上,through 只是接受一个函数并通过它运行this,composing 生成一个执行实际组合的函数,它们可以像这样一起使用:
Foo<D> fooD = fooAtoBtoCtoD
.through(fooA.composing())
.through(fooB.composing())
.through(fooC.composing());
【问题讨论】:
标签: java generics api-design