我不知道这个问题是否有一个特定的术语,但是有三种通用的解决方案:
- 避免使用具体类型来支持动态调度
- 在类型约束中允许占位符类型参数
- 通过使用关联的类型/类型族避免类型参数
当然是默认解决方案:继续拼写所有这些参数。
避免使用具体类型。
您已将Iterable
接口定义为:
interface <Element> Iterable<T: Iterator<Element>> {
getIterator(): T
}
这为界面用户提供了最大的能力,因为他们获得了确切T
的迭代器具体类型。这也使编译器可以应用更多优化,例如内联。
但是,如果Iterator<E>
是动态调度的接口,则不需要知道具体类型。例如,这就是Java使用的解决方案。该接口将被写为:
interface Iterable<Element> {
getIterator(): Iterator<Element>
}
Rust的一种有趣的变化是Rust的impl Trait
语法,它使您可以使用抽象的返回类型声明函数,但要知道具体的类型将在调用站点处知道(因此可以进行优化)。这与隐式类型参数的行为类似。
允许占位符类型参数。
该Iterable
接口并不需要了解的元素类型,所以有可能写这个为:
interface Iterable<T: Iterator<_>> {
getIterator(): T
}
其中T: Iterator<_>
表达约束“ T是任何迭代器,而与元素类型无关”。更严格地说,我们可以表示为:“存在某种类型,Element
所以它T
是Iterator<Element>
”,而不必知道的任何具体类型Element
。这意味着type-expression Iterator<_>
不描述实际的类型,而只能用作类型约束。
使用类型族/关联类型。
例如,在C ++中,类型可以具有类型成员。这在整个标准库中都是常用的,例如std::vector::value_type
。这并不能真正解决所有情况下的类型参数问题,但是由于类型可以引用其他类型,因此单个类型参数可以描述整个相关类型家族。
让我们定义:
interface Iterator {
type ElementType
fn next(): ElementType
}
interface Iterable {
type IteratorType: Iterator
fn getIterator(): IteratorType
}
然后:
class Vec<Element> implement Iterable {
type IteratorType = VecIterator<Element>
fn getIterator(): IteratorType { ... }
}
class VecIterator<T> implements Iterator {
type ElementType = T
fn next(): ElementType { ... }
}
这看起来非常灵活,但是请注意,这会使表达类型约束更加困难。例如,编写时Iterable
不强制执行任何迭代器元素类型,我们可能需要声明interface Iterator<T>
。您现在正在处理相当复杂的类型演算。偶然使这样的类型系统变得不确定是非常容易的(或者可能已经是?)。
请注意,关联类型可以非常方便地用作类型参数的默认值。例如,假设Iterable
接口需要用于元素类型的单独的类型参数,该参数通常(但不总是)与迭代器元素类型相同,并且我们拥有占位符类型参数,则可以这样说:
interface Iterable<T: Iterator<_>, Element = T::Element> {
...
}
但是,这只是语言的人体工程学功能,并不能使语言更强大。
类型系统很困难,因此最好看看在其他语言中哪些有效,哪些无效。
例如,考虑阅读Rust Book中的Advanced Traits一章,其中讨论了相关的类型。但是请注意,某些支持关联类型而不是泛型的观点仅适用于此,因为该语言不具有子类型化功能,并且每个特征最多只能对每种类型实现一次。即Rust特征不是类似于Java的接口。
其他有趣的类型系统包括具有各种语言扩展的Haskell。OCaml 模块/函数是类型系列的相对普通的版本,没有直接将它们与对象或参数化类型混合在一起。Java以其类型系统的局限性而著称,例如具有类型擦除功能的泛型,而没有值类型的泛型。C#非常类似于Java,但是设法避免了大多数这些限制,但代价是实现实现的复杂性增加。Scala尝试在Java平台上将C#样式的泛型与Haskell样式的类型类集成在一起。对C ++的看似简单的模板进行了深入研究,但与大多数泛型实现不同。
还值得研究这些语言的标准库(尤其是列表或哈希表之类的标准库集合),以了解常用的模式。例如,C ++具有一个具有不同迭代器功能的复杂系统,而Scala将细粒度的收集功能编码为特征。Java标准库接口有时是不完善的,例如Iterator#remove()
,但是可以将嵌套类用作一种关联类型(例如Map.Entry
)。