型パラメーターの型引数を推論する手法の名前は?


9

セットアップ:Iterator型パラメーターを持つと呼ばれる型があるとしましょうElement

interface Iterator<Element> {}

次に、をIterable返す1つのメソッドを持つインターフェースがありますIterator

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

Iteratorジェネリックであることの問題は、型引数を指定する必要があることです。

これを解決する1つのアイデアは、イテレータのタイプを「推測」することです。次の疑似コードは、Elementへの型引数であると推定される型変数があるという考えを表していますIterator

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

そして、次のような場所で使用します。

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

このの定義は、その定義の他の場所ではIterable使用していませんElementが、実際の使用例では使用しています。を使用する特定の関数は、双方向のイテレータなど、特定の種類のイテレータのみを返すs Iterableを受け入れるようにパラメータを制約できる必要もあります。そのIterableため、返されるイテレータは要素タイプだけではなくパラメータ化されます。


質問:

  • これらの推定された型変数の確立された名前はありますか?テクニック全体についてはどうですか?特定の命名法を知らないために、この例を実際に検索したり、言語固有の機能について学ぶことが困難になっています。
  • 総称を持つすべての言語がこの手法を備えているわけではありません。これらの言語で同様の技術の名前はありますか?

1
コンパイルしたいがコンパイルできないコードを示すことができますか?これで問題がより明確になると思います。
スイーパー

1
また、1つの言語を選択することもできます(または、使用している言語と構文の意味を伝えます)。たとえば、C#ではありません。「型推論」についての情報はインターネットで入手できますが、ここで当てはまるかどうかはわかりません。

5
ジェネリックを言語で実装しています。コンパイルする1つの言語でコードを取得しようとはしていません。これは、命名とデザインの問題でもあります。これが、ある程度不可知論である理由です。用語を知らないと、既存の言語の例やドキュメントを見つけるのが難しくなります。確かにこれは固有の問題ではありませんか?
Levi Morrison

回答:


2

この問題に特定の用語があるかどうかはわかりませんが、解決策には3つの一般的なクラスがあります。

  • 動的ディスパッチを支持して具体的な型を避ける
  • 型制約でプレースホルダー型パラメーターを許可する
  • 関連するタイプ/タイプファミリーを使用してタイプパラメータを回避する

そしてもちろん、デフォルトのソリューション:これらのパラメーターをすべてスペルアウトし続けます。

具体的なタイプは避けてください。

Iterableインターフェースを次のように定義しました:

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

これによりT、イテレータの具体的なタイプを正確に取得できるため、インターフェースのユーザーに最大のパワーを提供します。これにより、コンパイラーはインライン化などの最適化をさらに適用できます。

ただし、Iterator<E>が動的にディスパッチされるインターフェースである場合、具象タイプを知る必要はありません。これは、たとえばJavaが使用するソリューションです。インターフェイスは次のように記述されます。

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

これの興味深いバリエーションはimpl Trait、抽象的な戻り値の型で関数を宣言できるRustの構文ですが、具体的な型は呼び出し側で認識されるため(最適化が可能です)。これは、暗黙の型パラメーターと同様に動作します。

プレースホルダータイプのパラメーターを許可します。

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の章を読んでみてください。関連する型について説明しています。ただし、言語はサブタイプを備えておらず、各トレイトはタイプごとに最大1回しか実装できないため、ジェネリックではなく関連タイプを支持するいくつかのポイントが適用されることに注意してください。つまり、Rustの特性はJavaのようなインターフェースではありません。

その他の興味深い型システムには、さまざまな言語拡張を備えたHaskellがあります。OCamlのモジュール/ファンクターは、オブジェクトやパラメーター化された型と直接混在しないタイプファミリーの比較的単純なバージョンです。Javaは、その型システムの制限、たとえば型消去を伴うジェネリック、値型を超えるジェネリックがないことで注目に値します。C#は非常にJavaに似ていますが、実装の複雑さが増す代わりに、これらの制限のほとんどを回避できます。Scalaは、C#スタイルのジェネリックスをJavaプラットフォームの上にあるHaskellスタイルの型クラスと統合しようとします。C ++の一見単純なテンプレートはよく研究されていますが、ほとんどのジェネリック実装とは異なります。

これらの言語の標準ライブラリ(特にリストやハッシュテーブルなどの標準ライブラリコレクション)を見て、どのパターンが一般的に使用されているかを確認することも価値があります。たとえば、C ++にはさまざまなイテレーター機能の複雑なシステムがあり、Scalaは細かい収集機能を特性としてエンコードします。たとえばIterator#remove()、Java標準ライブラリインターフェースは適切ではない場合がありますが、ネストされたクラスを一種の関連型(たとえばMap.Entry)として使用できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.