私はコードを書くよりも頻繁にコードを読んでおり、産業用ソフトウェアに取り組んでいるプログラマーのほとんどがこれを行うと想定しています。私が想定している型推論の利点は、冗長性が少なく、記述されたコードが少ないことです。しかし、その一方で、コードをより頻繁に読み取る場合は、おそらく読み取り可能なコードが必要になります。
コンパイラはタイプを推測します。これには古いアルゴリズムがあります。しかし、本当の問題は、なぜプログラマーである私がコードを読んだときに変数の型を推測したいのかということです。型を読むだけの方が、どの型があるのかを考えるよりも速くはないですか?
編集:結論として、なぜそれが役立つのか理解しています。しかし、言語機能のカテゴリでは、演算子のオーバーロードがあるバケットで見られます-場合によっては便利ですが、乱用すると読みやすさに影響します。
Javaを見てみましょう。 Java推論されたタイプの変数を持つことはできません。これは、たとえタイプが何であるかが人間の読者に完全に明らかであるとしても、私は頻繁にタイプを綴る必要があることを意味します:
int x = 42; // yes I see it's an int, because it's a bloody integer literal!
// Why the hell do I have to spell the name twice?
SomeObjectFactory<OtherObject> obj = new SomeObjectFactory<>();
そして時々それはタイプ全体を綴るのが単に面倒なだけです。
// this code walks through all entries in an "(int, int) -> SomeObject" table
// represented as two nested maps
// Why are there more types than actual code?
for (Map.Entry<Integer, Map<Integer, SomeObject<SomeObject, T>>> row : table.entrySet()) {
Integer rowKey = entry.getKey();
Map<Integer, SomeObject<SomeObject, T>> rowValue = entry.getValue();
for (Map.Entry<Integer, SomeObject<SomeObject, T>> col : rowValue.entrySet()) {
Integer colKey = col.getKey();
SomeObject<SomeObject, T> colValue = col.getValue();
doSomethingWith<SomeObject<SomeObject, T>>(rowKey, colKey, colValue);
}
}
この冗長な静的型付けは、プログラマーの邪魔になります。ほとんどのタイプアノテーションは、繰り返し行われる行フィラーであり、すでに知っている内容のコンテンツを含まない逆流です。ただし、私は静的型付けが好きです。これは、バグの発見に本当に役立つため、動的型付けを使用することが常に良い答えとは限らないからです。型推論は両方の長所です。無関係な型は省略できますが、プログラムが(型-)チェックアウトすることを確認してください。
型推論はローカル変数に非常に役立ちますが、明確に文書化する必要があるパブリックAPIには使用しないでください。そして、型はコードで何が起こっているかを理解するために本当に重要な場合があります。このような場合、型推論のみに依存するのは愚かです。
型推論をサポートする多くの言語があります。例えば:
C++。 auto
キーワードは、型推論をトリガーします。それがなければ、ラムダまたはコンテナー内のエントリーのタイプをスペルアウトするのは地獄です。
C#。 var
を使用して変数を宣言できます。これにより、型推論の限定された形式がトリガーされます。それでも、型推論が必要なほとんどのケースが管理されます。特定の場所では、タイプを完全に省略することができます(例:ラムダ)。
Haskell、およびMLファミリーの任意の言語。ここで使用される型推論の特定のフレーバーは非常に強力ですが、関数の型注釈がよく見られます。これには2つの理由があります。1つ目はドキュメントであり、2つ目は型推論が実際に期待する型を見つけたかどうかのチェックです。不一致がある場合は、何らかのバグがある可能性があります。
コードが書き込まれるよりもはるかに頻繁に読み取られるのは事実です。ただし、読み取りにも時間がかかり、2画面のコードは1画面のコードよりもナビゲートして読み取るのが難しいため、最も役立つ情報/読み取り労力の比率をパックするように優先する必要があります。これは一般的なUXの原則です。一度に多すぎる情報は圧倒され、実際にインターフェースの有効性を低下させます。
そして、しばしば、正確なタイプ重要ではない(それ)が重要なのは私の経験です。確かにあなたは時々式を入れ子にします:_x + y * z
_、monkey.eat(bananas.get(i))
、factory.makeCar().drive()
。これらのそれぞれには、型が書き出されていない値に評価される部分式が含まれています。しかし、それらは完全に明確です。コンテキストから理解するのは十分簡単であり、書き出すことは良いことよりも害を及ぼすため、タイプを記述せずにそのままでも大丈夫です(データフローの理解を混乱させ、貴重な画面と短期的なメモリ空間をとります)。
明日がないように式をネストしない理由の1つは、行が長くなり、値のフローが不明確になるためです。一時変数の導入はこれに役立ち、順序を課し、部分的な結果に名前を付けます。ただし、これらの側面から利益を得るすべてが、そのタイプを綴ることによって利益を得るわけではありません。
_user = db.get_poster(request.post['answer'])
name = db.get_display_name(user)
_
user
がエンティティオブジェクト、整数、文字列、その他のどれであるかは重要ですか?ほとんどの目的ではそうではありません。ユーザーを表し、HTTPリクエストからのものであり、名前をフェッチして回答の右下隅に表示することを知っていれば十分です。
そして、それが行う問題である場合、作成者はタイプを自由に書き出すことができます。これは責任を持って使用する必要がある自由ですが、読みやすさを向上させる他のすべて(変数名と関数名、フォーマット、APIデザイン、空白)についても同じです。そして確かに、HaskellとML(ここでeverythingは余計な努力なしに推論できる)の規約は、非ローカル関数の型を書き出すことです、また、必要に応じてローカル変数と関数も使用します。初心者だけがすべてのタイプを推測させます。
型推論は非常に重要であり、現代の言語でサポートされるべきだと思います。私たちはすべてIDEで開発しており、推論された型を知りたい場合に備えて、それらはvi
をハッキングするごく一部にすぎません。たとえば、Javaの冗長性と式のコードを考えてみてください。
Map<String,HashMap<String,String>> map = getMap();
しかし、あなたが私のIDEが私を助けて大丈夫だと言うことができます、それは有効なポイントであるかもしれません。ただし、一部の機能は、たとえばC#の匿名型など、型の推論を利用しないと実現できません。
var person = new {Name="John Smith", Age = 105};
Linqは、型推論の助けがなければ今ほど素敵ではありません。たとえば、Select
var result = list.Select(c=> new {Name = c.Name.ToUpper(), Age = c.DOB - CurrentDate});
この匿名の型は、変数にきちんと推論されます。
Scala
での戻り値の型の型推論は嫌いです。あなたの指摘はここに当てはまると思います。APIをより流暢に使用できるように、関数が何を返すかは明らかです。
これに対する答えは本当に簡単だと思います。冗長な情報の読み書きを節約できます。特に、等号の両側に型があるオブジェクト指向言語ではそうです。
これはまた、それを使用する必要がある場合と使用しない場合、つまり情報が冗長でない場合にもわかります。
コードを見たとしましょう:
someBigLongGenericType variableName = someBigLongGenericType.someFactoryMethod();
someBigLongGenericType
がsomeFactoryMethod
の戻り値の型から割り当て可能な場合、コードを読んだ人が型が正確に一致していない場合に気付く可能性、および不一致に気付いた人はどれほど容易にそれが意図的であったかどうかを認識しますか?
推論を許可することで、言語はコードを読んでいる人に、変数の型が明示的に述べられたときにその理由を見つけようとするように提案できます。これにより、コードを読んでいる人は自分の努力に集中できるようになります。対照的に、タイプが指定されている大部分の場合、それがたまたま推測されたものとまったく同じである場合、コードを読んでいる人は、微妙に異なる時間に気づきにくいかもしれません。 。
すでに多くのすばらしい答えがあることがわかります。そのうちのいくつかは繰り返しますが、時々あなたは自分の言葉で物事を表現したいだけです。 C++の例をいくつか紹介します。これは、C++が最も親しんでいる言語だからです。
必要なことは決して賢明ではありません。型推論は、他の言語機能を実用的にするために必要です。 C++では、発話できない型を使用することができます。
_struct {
double x, y;
} p0 = { 0.0, 0.0 };
// there is no name for the type of p0
auto p1 = p0;
_
C++ 11はラムダを追加しましたが、これも発話できません。
_auto sq = [](int x) {
return x * x;
};
// there is no name for the type of sq
_
型推論もテンプレートを支えています。
_template <class x_t>
auto sq(x_t const& x)
{
return x * x;
}
// x_t is not known until it is inferred from an expression
sq(2); // x_t is int
sq(2.0); // x_t is double
_
しかし、あなたの質問は、「なぜプログラマーは、コードを読むときに変数の型を推測したいのか?型を読み取るだけの方が、型が何であるかを考えるよりも速くないのではないか」でした。
型推論は冗長性を削除します。コードの読み取りに関しては、コードに冗長な情報を含める方が高速で簡単な場合がありますが、冗長性は有用な情報に影を付ける可能性があります。例えば:
_std::vector<int> v;
std::vector<int>::iterator i = v.begin();
_
C++プログラマーがiがi = v.begin()
からのイテレーターであることを特定するために、標準ライブラリーにあまり慣れていないため、明示的な型宣言の値は限定されています。その存在により、より重要な詳細がわかりにくくなります(たとえば、i
がベクターの先頭を指しているなど)。 @amonによる良い答えは、重要な詳細を覆い隠す冗長性のさらに良い例を提供します。対照的に、型推論を使用すると、重要な詳細がより目立ちます。
_std::vector<int> v;
auto i = v.begin();
_
コードの読み取りは重要ですが、それだけでは不十分です。ある時点で、読み取りを停止して新しいコードの作成を開始する必要があります。 コードの冗長性により、コードの変更が遅くなり、困難になります。たとえば、次のコードの断片があるとします。
_std::vector<int> v;
std::vector<int>::iterator i = v.begin();
_
ベクトルの値の型を変更してコードを二重に変更する必要がある場合:
_std::vector<double> v;
std::vector<double>::iterator i = v.begin();
_
この場合、2つの場所でコードを変更する必要があります。元のコードが次のような型推論とは対照的です。
_std::vector<int> v;
auto i = v.begin();
_
そして変更されたコード:
_std::vector<double> v;
auto i = v.begin();
_
コードを1行変更するだけでよいことに注意してください。これを大規模なプログラムに外挿すると、型の推論により、エディターを使用する場合よりもはるかに迅速に型への変更を伝達できます。
コードの冗長性により、バグが発生する可能性があります。コードが2つの情報に等しく依存していることに依存している場合は常に、誤りの可能性があります。たとえば、このステートメントの2つのタイプの間に不整合がありますが、これはおそらく意図されていません。
_int pi = 3.14159;
_
冗長性は意図を識別しにくくします。明示的な型指定よりも単純なため、場合によっては型推論が読みやすく、理解しやすい場合があります。コードの断片を考えてみましょう:
_int y = sq(x);
_
sq(x)
がint
を返す場合、y
がint
かどうかは明らかではありません。これは、sq(x)
の戻り値の型であるため、またはy
を使用するステートメントに適しているためです。 sq(x)
がint
を返さないように他のコードを変更した場合、その行だけではy
のタイプを更新する必要があるかどうかは不明です。同じコードと比較してくださいが、型推論を使用しています:
_auto y = sq(x);
_
この意図は明らかで、y
はsq(x)
によって返されるものと同じ型でなければなりません。コードがsq(x)
の戻り値の型を変更すると、y
の型が自動的に一致するように変更されます。
C++では、上記の例が型推論でより単純になる2つ目の理由があります。型推論では暗黙の型変換を導入できません。 sq(x)
の戻り値の型がint
ではない場合、コンパイラは暗黙的にint
への暗黙の変換を挿入します。 sq(x)
の戻り値の型がoperator int()
を定義する複合型である場合、この非表示の関数呼び出しは任意に複雑になる可能性があります。