在方法范围内限制通用参数

时间:2018-07-18 15:16:30

标签: java generics api-design

比方说,我们有一个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

1 个答案:

答案 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));

Here's a working example.

这实际上并不短,但是它在做什么方面更加清楚。

不幸的是,对于abc.compose(a).compose(b)之类的希望,目前还没有Java。

我认为Java编译器原则上可以支持某种实例方法级别的泛型,例如<A,B> where 'this' must be a Foo<Function<A,B>>。除了在方法调用站点检查引用的类型外,它不需要做任何其他事情,因此它并不像擦除阻止它一样。

通常,当Java缺少泛型功能时,这是因为擦除使它无法像泛型构造函数调用那样实现,但我认为情况并非如此。方法主体(要求TFunction)可以擦除为((Function) this.value).apply(a)之类的强制类型转换。