型推論のトレードオフは何ですか?


29

すべての新しいプログラミング言語、または少なくとも一般的になったプログラミング言語は型推論を使用しているようです。Javascriptでさえ、さまざまな実装(Acscript、typescriptなど)を通じて型と型推論を取得しました。私には良さそうに見えますが、トレードオフがあるのか​​、Javaや古い良い言語に型推論がないのはなぜだと思いますか

  • 型を指定せずにGoで変数を宣言すると(型なしのvarまたは:=構文を使用)、変数の型は右側の値から推測されます。
  • Dを使用すると、動的言語のように、型を重複して指定せずに大きなコードフラグメントを作成できます。一方、静的推論は型と他のコードプロパティを推測し、静的な世界と動的な世界の両方の長所を提供します。
  • Rustの型推論エンジンは非常にスマートです。これは、初期化中にr値のタイプを見る以上のことを行います。また、変数のタイプを推測するために後で変数がどのように使用されるかについても調べます。
  • Swiftは型推論を使用して適切な型を算出します。型推論により、コンパイラは、指定した値を調べるだけで、コードをコンパイルするときに特定の式の型を自動的に推測できます。

3
C#の一般的なガイドラインでは、読みやすさを損なうことがあるため、常に使用するとは限らないvarことが示されています。
Mephy

5
「...または、なぜJavaまたは古い良い言語には型推論がないとしましょうか」理由はおそらく歴史的です。ウィキペディアによると、MLはCの1年後に出現し、型推論が行われました。JavaはC ++開発者にアピールしようとしていました。C ++はCの拡張として始まり、Cの主な関心事は、アセンブリの移植可能なラッパーであり、コンパイルが容易であることでした。それが価値があるものは何でも、私はサブタイプ化が一般的な場合にも型推論を決定不能にすることを読んだ。
ドーバル

3
@Doval Scalaは、サブタイプの継承をサポートする言語のために、タイプの推測でかなり良い仕事をしているようです。MLファミリ言語のどれよりも優れていませんが、言語の設計を考えると、おそらくあなたが求めることができるほど優れています。
KChaloux

12
それは間の区別描画する価値がある型推論(C#のような単方向、varおよびC ++ auto)と型推論(Haskellのような、双方向にlet)。前者の場合、名前のタイプはイニシャライザーのみから推測できます。名前のタイプに従って使用する必要があります。後者の場合、名前のタイプはその使用からも推測できます。これは[]、要素のタイプに関係なく空のシーケンスに対して、またはnewEmptyMVar参照先に関係なく新しいnull可変参照に対して簡単に記述できるという点で便利です。タイプ。
ジョンパーディ

型推論の実装は、特に効率的に非常に複雑になる可能性があります。コンパイルの複雑さが、より複雑な式による指数関数的な爆発に苦しむことを望まない。興味深い読み物:cocoawithlove.com/blog/2016/07/12/type-checker-issues.html
アレクサンダー-モニカの復活

回答:


43

Haskellの型システムは完全に推論可能です(ポリモーフィックな再帰、特定の言語 拡張、および恐ろしい単相性の制限は別として)が、プログラマーは、必要がない場合でもソースコードで型注釈を頻繁に提供します。どうして?

  1. 型注釈はドキュメントとして機能します。これは、Haskellのような表現力豊かな型の場合に特に当てはまります。関数の名前と型を考えると、通常、関数がパラメトリックにポリモーフィックである場合は特に、関数が何をするかをかなりよく推測できます。
  2. 型注釈は開発を促進できます。関数の本体を記述する前に型シグネチャを記述することは、テスト駆動開発のようなものです。私の経験では、Haskell関数をコンパイルすると、通常は最初に機能します。(もちろん、これは自動テストの必要性を取り除くわけではありません!)
  3. 明示的な型は、仮定を確認するのに役立ちます。既に動作するコードを理解しようとするとき、私は頻繁に、正しい型注釈であると信じるものをペッパーに入れます。コードがまだコンパイルされる場合、私はそれを理解したことを知っています。そうでない場合は、エラーメッセージを読みます。
  4. 型シグネチャを使用すると、多態性関数を特殊化できます。非常にまれに、特定の機能が多態性でない場合、APIの方が表現力があり便利です。関数に推論されるより一般的でない型を関数に与えても、コンパイラは文句を言いません。古典的な例はmap :: (a -> b) -> [a] -> [b]です。より一般的な形式(fmap :: Functor f => (a -> b) -> f a -> f b)はFunctor、リストだけでなくすべてのに適用されます。しかしmap、初心者の方が理解しやすいと感じたため、兄と一緒に暮らしています。

全体的に、静的に型付けされた推論可能なシステムの欠点は、一般的に静的型付けの欠点とほぼ同じです。このサイトや他のサイトでよく議論されていますページのフレーム戦争)。もちろん、上記の欠点のいくつかは、推論可能なシステムの型注釈の量が少なくなることで改善されます。さらに、型推論には独自の利点があります。型推論なしではホール駆動型の開発は不可能です。

Java *は、あまりにも多くの型注釈を必要とする言語が煩わしいことを証明しますが、少なすぎると上記の利点を失うことになります。オプトアウト型の推論を使用する言語は、2つの両極端のバランスが取れています。

*その素晴らしいスケープゴートであるJavaでさえ、ある程度のローカル型推論を実行します。のようなステートメントMap<String, Integer> = new HashMap<>();では、コンストラクターのジェネリック型を指定する必要はありません。一方、MLスタイルの言語は通常、グローバルに推論可能です。


2
しかし、あなたは初心者ではありません;)実際に「取得」する前に、型クラスと種類を理解する必要がありますFunctor。リストを作成し、map調味料を使用していないHaskellerに馴染みがあります。
ベンジャミンホジソン

1
C#varは型推論の例だと思いますか?
ベンジャミンホジソン

1
はい、C#varは適切な例です。
ドーバル

1
このサイトはすでにこの議論が長すぎると報告しているので、これが最後の返信になります。前者では、コンパイラはx; の型を決定する必要があります。後者では、決定する型はなく、すべての型が既知であり、式が意味をなすことを確認するだけです。違いは、些細な例を過ぎて移動するにつれて重要になりx、複数の場所で使用されます。コンパイラは、x1)を決定するために使用される場所をクロスチェックする必要があります1)xコードが型チェックするように型を割り当てることは可能ですか?2)割り当てられている場合、割り当てることができる最も一般的なタイプは何ですか?
ドーバル

1
new HashMap<>();構文はJava 7でのみ追加され、Java 8のラムダは非常に多くの「実際の」型推論を可能にすることに注意してください。
マイケルボルグ

8

C#では、型推論はコンパイル時に発生するため、ランタイムコストはゼロです。

スタイルの問題としてvar、タイプを手動で指定するのが不便または不要な状況で使用されます。Linqはそのような状況の1つです。もう一つは:

var s = new SomeReallyLongTypeNameWith<Several, Type, Parameters>(andFormal, parameters);

それなしでは、単に言うのではなく、本当に長い型名(および型パラメーター)を繰り返すことになりますvar

明示的であるとコードの明瞭さが向上する場合、タイプの実際の名前を使用します。

構築時に値が設定されるメンバー変数宣言など、型推論を使用できない状況や、インテリセンスを適切に動作させたい場合があります(明示的に型を宣言しない限り、HackerrankのIDEは変数のメンバーをインテリセンスしません) 。


16
「C#では、型推論はコンパイル時に発生するため、ランタイムコストはゼロです。」型推論は、定義によりコンパイル時に発生するため、すべての言語に当てはまります。
ドーバル

とても良いです。
ロバートハーベイ

1
@Dovalでは、JITコンパイル中に型推論を実行できますが、これには明らかにランタイムコストがかかります。
ジュール

6
@Julesプログラムを実行している限り、すでに型チェックされています。推論するものは何もありません。あなたが話していることは、通常、型推論と呼ばれていませんが、適切な用語が何であるかはわかりません。
ドーバル

7

良い質問!

  1. 型には明示的に注釈が付けられていないため、コードが読みにくくなることがあり、バグが増えます。正しくはもちろん、それを使用するコードクリーナーとなり、より読みやすいし。あなたが悲観論者で、ほとんどのプログラマーが悪いと思う(またはほとんどのプログラマー悪いところで働く)と、これは純損失になります。
  2. 型推論アルゴリズムは比較的単純ですが、無料ではありません。この種のことは、コンパイル時間をわずかに増加させます。
  3. 型には明示的に注釈が付けられていないため、IDEは、あなたがしようとしていることを推測することもできず、宣言プロセス中にオートコンプリートや同様のヘルパーに損害を与えます。
  4. 関数のオーバーロードと組み合わせることで、型推論アルゴリズムがどのパスを取るかを決定できない状況に陥ることがあり、,いキャストスタイルの注釈につながります。(これは、たとえばC#の匿名関数構文でかなり発生します)。

また、明示的な型注釈がなければ奇妙なことを行うことができない難解な言語があります。これまでのところ、通過することを除いて言及するのに十分な一般的/人気/有望であると私が知っているものはありません。


1
オートコンプリートは、型推論についてのたわごとを与えません。タイプがどのように決定されたかは関係なく、タイプが何であるかだけが重要です。また、C#のラムダの問題は推論とは関係ありません。ラムダは多相ですが、型システムはそれに対処できないためです。推論とは関係ありません。
DeadMG

2
@deadmg-確かに、変数が宣言されると問題はありませんが、変数宣言を入力している間は、宣言された型がないため、右側のオプションを宣言された型に絞り込むことはできません。
テラスティン

@deadmg-ラムダに関しては、あなたが何を意味するかについてはあまり明確ではありませんが、物事がどのように機能するかについての私の理解に基づいて、それが完全に正しいわけではないと確信しています。var foo = x => x;言語はxここで推論する必要があり、続行するものがないため、失敗するほど単純なものでさえもです。ラムダが構築されると、ラムダが生成され、明示的に型指定された関数として構築されるように、明示的に型指定されたデリゲートFunc<int, int> foo = x => xがCILにFunc<int, int> foo = new Func<int, int>(x=>x);組み込まれるため、構築されます。私には、の種類を推測することはx型推論の一部と小包です...
Telastyn

3
@Telastynこの言語には何もすることがないのは事実ではありません。式を入力するために必要なすべての情報x => xは、ラムダ自体の中に含まれています。周囲のスコープの変数を参照しません。まともな関数型言語では正しくそのタイプは「すべてのタイプのためのものであることを推測するだろうaa -> a」。DeadMGが言おうとしているのは、C#の型システムにはプリンシパル型プロパティがないため、どの式でも最も一般的な型を常に把握できるということです。破壊するのは非常に簡単なプロパティです。
ドーバル

@Doval-enh ... C#には汎用関数オブジェクトのようなものはありません。同じトラブルにつながるとしても、あなたが話しているものとは微妙に異なると思います。
テラスティン

4

私には良さそうに見えますが、トレードオフがあるのか​​、Javaや古い良い言語に型推論がないのはなぜだと思いますか

Javaは、ここでの規則ではなく例外です。C ++(これは "古き良き言語"であると信じています)でさえauto、C ++ 11標準以来、キーワードによる型推論をサポートしています。変数宣言だけでなく、関数の戻り値の型としても機能します。これは、いくつかの複雑なテンプレート関数で特に便利です。

暗黙的な型付けと型推論には多くの優れたユースケースがあり、実際にそうすべきではないユースケースもいくつかあります。これは時々好みの問題であり、議論の対象でもあります。

しかし、間違いなく良いユースケースが存在することは、それを実装する言語の正当な理由そのものです。また、機能の実装が難しくなく、実行時のペナルティがなく、コンパイル時間に大きな影響を与えません。

開発者に型推論を使用する機会を与えることに、本当の欠点はないと思います。

一部の回答者は、明示的なタイピングがときどきどれほど優れているかを推論し、それらは確かに正しいです。しかし、暗黙的な型指定をサポートしないということは、言語が常に明示的な型指定を強制することを意味します。

したがって、実際の欠点、言語暗黙的な型付けをサポートしていない場合です。これにより、開発者がそれを使用する正当な理由がないと述べているためです。


1

Hindley-Milnerタイプの推論システムとGoスタイルのタイプ推論の主な違いは、情報の流れの方向です。HMでは、型情報は統合によって前後に流れます。Goでは、タイプ情報はフローのみを転送します。フォワード置換のみを計算します。

HM型推論は、多態的に型付けされた関数型言語でうまく機能する素晴らしい革新ですが、Goの著者はおそらく多すぎることを試みていると主張するでしょう。

  1. 情報が順方向と逆方向の両方に流れるという事実は、HM型推論が非常に非局所的な問題であることを意味します。型注釈がない場合、プログラムのすべてのコード行が1行のコードの入力に寄与する可能性があります。前方置換のみを使用している場合、型エラーの原因はエラーの前のコードにある必要があることがわかります。後にくるコードではありません。

  2. HM型推論では、制約を考慮する傾向があります:型を使用する場合、可能な型を制限します。一日の終わりには、完全に制約されないままになっている型変数が存在する場合があります。HM型推論の洞察は、これらの型は実際には重要ではないため、多型変数になっているということです。ただし、この余分な多型は、いくつかの理由で望ましくない場合があります。第一に、一部の人々が指摘したように、この余分なポリモーフィズムは望ましくない可能性があります。第二に、型がポリモーフィックのままになると、実行時の動作に影響を与える可能性があります。たとえば、過度に多形的な中間結果が 'showの理由です。Haskellではread 'は曖昧と見なされます。別の例として、


2
残念ながら、これは別の質問に対する良い答えのように見えます。OPは、「Go-style」とHMではなく、「Go-style型推論」と、まったく型推論のない言語について質問しています。
Ixrec

-2

可読性が損なわれます。

比較する

var stuff = someComplexFunction()

List<BusinessObject> stuff = someComplexFunction()

githubのようにIDEの外を読むとき、それは特に問題です。


4
これは作られたポイントを超える大幅な提供の何にも思えるし、前6つの回答で説明していません
ブヨ

「複雑な読み取りvar不可」の代わりに適切な名前を使用すると、読みやすさを損なうことなく使用できます。var objects = GetBusinessObjects();
ファビオ

それでは、型システムを変数名にリークしています。それはハンガリーの記法のようなものです。リファクタリング後、コードと一致しない名前で終わる可能性が高くなります。一般に、関数型スタイルは、クラスが提供する自然な名前空間の利点を失うことを余儀なくさせます。したがって、square.area()の代わりにsquareArea()が追加されます。
ミゲル
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.