用于推断类型参数的类型参数的技术名称?


9

设置:假设我们有一个名为type的类型Iterator,该类型具有type参数Element

interface Iterator<Element> {}

然后我们有一个接口Iterable,该接口具有一个将返回的方法Iterator

// T has an upper bound of Iterator
interface Iterable<T: Iterator> {
    getIterator(): T
}

Iterator泛型的问题是我们必须为其提供类型参数。

解决此问题的一种方法是“推断”迭代器的类型。以下伪代码表达了这样的想法:存在一个类型变量Element,该类型变量被推断为Iterator

interface <Element> Iterable<T: Iterator<Element>> {
    getIterator(): T
}

然后我们在这样的地方使用它:

class Vec<Element> implements Iterable<VecIterator<Element>> {/*...*/}

这个定义在其定义中Iterable没有使用Element任何其他地方,但在我的实际用例中却使用了。使用的某些函数Iterable还需要能够约束其参数以接受Iterables,该s仅返回某些迭代器,例如双向迭代器,这就是为什么返回的迭代器被参数化而不是元素类型的原因。


问题:

  • 这些推断的类型变量是否有确定的名称?整个技术呢?由于不知道特定的术语,因此很难在野外搜索此示例或了解特定于语言的功能。
  • 并非所有具有泛型的语言都具有此技术;这些语言中有类似技术的名称吗?

1
您可以显示一些未编译的代码吗?我认为这将使问题更明确。
清道夫'18

1
您可能还会选择一种语言(或说出您使用的是哪种语言,以及语法的含义)。显然不是,例如C#。互联网上有很多有关“类型推断”的信息,但我不确定它是否适用于此。

5
我正在用一种语言实现泛型,而不是试图用任何一种语言来编译代码。这也是一个命名和设计问题。这就是为什么它不可知的原因。在不知道术语的情况下,很难找到现有语言的示例和文档。当然这不是一个独特的问题吗?
维·莫里森

Answers:


2

我不知道这个问题是否有一个特定的术语,但是有三种通用的解决方案:

  • 避免使用具体类型来支持动态调度
  • 在类型约束中允许占位符类型参数
  • 通过使用关联的类型/类型族避免类型参数

当然是默认解决方案:继续拼写所有这些参数。

避免使用具体类型。

您已将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所以它TIterator<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)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.