比方说,我们有一个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
}
现在,我可以轻松地将fooAtoB
类型的Foo<Function<A, B>>
与fooA
类型的Foo<A>
组成,像这样:
compose(fooAtoB, fooA)
不幸的是,在我的情况下,Foo
会产生像Function<A, Function<B, Function<C, D>>>
这样的嵌套函数,而我希望能够将它们与Foo<A>
,Foo<B>
组合在一起和Foo<C>
得到一个Foo<D>
。
我可以执行以下操作:
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
的{{1}}参数限制为类型的子集,以便可以利用该限制? / p>
可以让我表达以下内容的东西很棒:
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);
}
在我看来,这是迄今为止最好的解决方案。
编辑:Radiodef的解决方案为我提供了一个使用相同界面的更简单解决方案的想法:
fooAtoBtoCtoD.compose(fooA.wrap()).compose(fooB.wrap()).compose(fooC.wrap())
本质上,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
答案 0 :(得分:1)
另一个变种是例如下列方法:
class Foo<T> {
...
<U> Foo<U> map(Function<T, U> fn) {
return new Foo<>(fn.apply(this.value));
}
static <T, U> Function<Function<T, U>, U> composing(Foo<T> t) {
return fn -> fn.apply(t.value);
}
}
那会让你做例如这个:
Foo<D> fooD =
fooAtoBtoCtoD.map(composing(fooA))
.map(composing(fooB))
.map(composing(fooC));
这实际上并不短,但是它在做什么方面更加清楚。
不幸的是,对于abc.compose(a).compose(b)
之类的希望,目前还没有Java。
我认为Java编译器原则上可以支持某种实例方法级别的泛型,例如<A,B> where 'this' must be a Foo<Function<A,B>>
。除了在方法调用站点检查引用的类型外,它不需要做任何其他事情,因此它并不像擦除阻止它一样。
通常,当Java缺少泛型功能时,这是因为擦除使它无法像泛型构造函数调用那样实现,但我认为情况并非如此。方法主体(要求T
为Function
)可以擦除为((Function) this.value).apply(a)
之类的强制类型转换。