web-dev-qa-db-ja.com

弱く型付けされた言語に関する明らかな矛盾についての明確化を求める

私は 強いタイピング を理解していると思いますが、弱いタイピングの例を探すたびに、単純に型を自動的に強制/変換するプログラミング言語の例を見つけることになります。

たとえば、この記事では 入力:強いvs.弱い、静的vs.動的 は、Pythonは強く型付けされます。

Python

1 + "1"
Traceback (most recent call last):
File "", line 1, in ? 
TypeError: unsupported operand type(s) for +: 'int' and 'str'

ただし、このようなことはJavaおよびC#で可能です。そのためだけに弱く型付けされているとは考えていません。

Java

  int a = 10;
  String b = "b";
  String result = a + b;
  System.out.println(result);

C#

int a = 10;
string b = "b";
string c = a + b;
Console.WriteLine(c);

Weakly Type Languages という名前のこの別の記事では、明示的な変換なしで文字列を数字に連結したり、その逆に連結したりすることができるため、著者はPerlの型は弱いと述べています。

Perl

$a=10;
$b="a";
$c=$a.$b;
print $c; #10a

したがって、同じ例では、Perlは弱い型付けになりますが、Java and C#?.

うん、これはわかりにくい enter image description here

著者は、異なる型の値に対する特定の操作の適用を妨げる言語は強く型付けされ、反対は弱い型付けを意味することを暗示しているようです。

したがって、ある時点で、言語が多くの自動変換または型間の強制を提供する場合(Perlとして)弱い型付けと見なされる可能性がありますが、少数の変換のみを提供する他の言語は最終的に強く型付けされたとみなされます。

しかし、私はこの解釈を間違っているに違いないと信じています。それを説明する理由や方法がわからないだけです。

だから、私の質問は:

  • 言語が本当に弱く型付けされているとはどういう意味ですか?
  • 言語によって行われる自動変換/自動強制に関連しない弱いタイピングの良い例を挙げていただけますか?
  • 言語を弱く型付けし、同時に強く型付けすることはできますか?
176
Edwin Dalorzo

更新: この質問は2012年10月15日の私のブログの主題でした。 すばらしい質問をありがとう!


言語が「弱く型付けされる」とはどういう意味ですか?

これは、「この言語は私が嫌悪感を覚える型システムを使用している」という意味です。対照的に、「強く型付けされた」言語は、私が心地よいと思う型システムを持つ言語です。

これらの用語は本質的に意味がなく、避けるべきです。 ウィキペディア リスト「強く型付けされた」の11の異なる意味。そのいくつかは矛盾しています。これは、「強く型付けされた」または「弱く型付けされた」という用語を含む会話では、混乱が生じる可能性が高いことを示しています。

確実に言えることは、議論中の「強く型付けされた」言語には、実行時またはコンパイル時に型システムに追加の制限があり、議論中の「弱く型付けされた」言語にはないということです。その制限が何であるかは、さらなるコンテキストなしでは決定できません。

「強く型付けされた」および「弱い型付けされた」を使用する代わりに、どのようなタイプの安全性を意味するかを詳細に記述する必要があります。たとえば、C#はstatically typed言語であり、type safe言語ですメモリーセーフ言語、ほとんどの部分。 C#では、これら3つの形式の「強い」タイピングすべてに違反することができます。キャスト演算子は静的型付けに違反しています。それはコンパイラに「あなたよりもこの式の実行時の型について知っている」と言っています。開発者が間違っている場合、型安全性を保護するためにランタイムは例外をスローします。開発者がタイプセーフティまたはメモリセーフティを破りたい場合は、「安全でない」ブロックを作成してタイプセーフティシステムをオフにすることで実現できます。安全でないブロックでは、ポインターマジックを使用して、intをフロートとして扱う(型の安全性に違反する)か、所有していないメモリに書き込むことができます。 (メモリの安全性に違反します。)

C#は、コンパイル時と実行時の両方でチェックされる型制限を課すため、コンパイル時のチェックや実行時のチェックが少ない言語と比較して、「強く型付けされた」言語になります。また、C#を使用すると、特別な状況でこれらの制限を回避するエンドランを実行できるため、このようなエンドランを許可しない言語と比較して「弱い型付け」言語になります。

本当にどちらですか?言うことは不可能です。それは話者の視点とさまざまな言語機能に対する彼らの態度に依存します。

209
Eric Lippert

他の人が指摘したように、「強く型付けされた」と「弱く型付けされた」という用語は非常に多くの異なる意味を持っているため、あなたの質問に対する単一の答えはありません。ただし、質問で具体的にPerlについて言及しているので、Perlが弱く型付けされているという意味で説明してみましょう。

ポイントは、Perlには「整数変数」、「浮動変数」、「文字列変数」、「ブール変数」などがないことです。実際、ユーザーが(通常)知る限り、整数、浮動小数点数、文字列、ブール値もありません:あなたが持っているのは「スカラー」だけです。同じ時間。たとえば、次のように記述できます。

_$foo = "123" + "456";           # $foo = 579
$bar = substr($foo, 2, 1);      # $bar = 9
$bar .= " lives";               # $bar = "9 lives"
$foo -= $bar;                   # $foo = 579 - 9 = 570
_

もちろん、あなたが正しく注意しているように、これらのすべては単なるタイプの強制として見ることができます。しかし、ポイントは、Perlでは、型はalways強制されているということです。実際、ユーザーが変数の内部「タイプ」が何であるかを知ることは非常に困難です。上の例の2行目で、_$bar_の値が文字列_"9"_であるかどうかを尋ねます数値_9_は、Perlに関する限りこれらは同じものなので、ほとんど意味がありません。実際、Perlスカラーが内部的にbothを同時に持つことも可能です。たとえば、上記の行2の後の_$foo_の場合。

これの裏返しは、Perl変数は型付けされていない(つまり、内部型をユーザーに公開しない)ため、演算子をオーバーロードして異なる型の引数に対して異なる処理を実行できないことです。 「この演算子は数値にXを、文字列にYを行う」と言うことはできません。演算子は、その引数がどの種類の値であるかわからない(わからない)からです。

したがって、たとえば、Perlには数値加算演算子(_+_)と文字列連結演算子(_._)の両方があり、必要です:上記で見たように、文字列(_"1" + "2" == "3"_)または数値を連結します(_1 . 2 == 12_)。同様に、数値比較演算子_==_、_!=_、_<_、_>_、_<=_、_>=_および_<=>_は数値を比較します引数の値、文字列比較演算子eqneltgtlegeおよびcmpは、文字列として辞書的に比較します。したがって、_2 < 10_、ただし_2 gt 10_(ただし_"02" lt 10_、ただし_"02" == 2_)。 (注意してください、特定のother JavaScriptなどの言語は、also演算子のオーバーロードを行いながらPerlのような弱い型付けに対応しようとします。これはしばしば、結合性の喪失のようなさをもたらします。 _+_の場合)

(ここでの軟膏のフライは、歴史的な理由から、Perl 5にはビットごとの論理演算子のようないくつかのコーナーケースがあり、その動作は引数の内部表現に依存するということです。内部表現は驚くべき理由で変化する可能性があるため、特定の状況でこれらのオペレーターが何をするかを予測するのは難しい場合があります。)

そうは言っても、Perl doesには強い型があると主張できます。彼らはあなたが期待するような種類ではありません。具体的には、上で説明した「スカラー」型に加えて、Perlには「配列」と「ハッシュ」という2つの構造型もあります。これらはveryスカラーとは異なり、Perl変数が異なる点まで sigils タイプを示す(_$_はスカラー、_@_は配列、 _%_ハッシュ用)1。これらの型の間にはare強制ルールがあるため、canなどを記述します。 _%foo = @bar_、しかしそれらの多くは非常に損失が大きい:例えば、_$foo = @bar_は配列の長さを_@bar_にその内容ではなく_$foo_に割り当てます。 (また、タイプグロブやI/Oハンドルのような他の奇妙な型がいくつかありますが、これらは頻繁に公開されることはありません。)

また、この素敵なデザインのわずかな欠点は、特別な種類のスカラーである参照型の存在です(そして、canref演算子を使用して通常のスカラーと区別されます) 。参照を通常のスカラーとして使用することは可能ですが、それらの文字列/数値は特に有用ではなく、通常のスカラー演算を使用して参照を変更すると、特別な参照性を失う傾向があります。また、Perl変数2 クラスにblessedして、そのクラスのオブジェクトに変えることができます。 OO Perlのクラスシステムは、上記のプリミティブ型(または型なし)システムに多少直交していますが、 ダックタイピング =パラダイム一般的な意見は、Perlでオブジェクトのクラスをチェックしていることに気付いた場合、何か間違ったことをしているということです。


1 実際、シギルはアクセスされる値のタイプを示します。配列_@foo_の最初のスカラーは_$foo[0]_で示されます。詳細については、 perlfaq4 を参照してください。

2 Perlのオブジェクトは(通常)それらへの参照を通じてアクセスされますが、実際にblessedを取得するのは、参照が指す(おそらく匿名の)変数です。しかし、祝福は確かに変数のプロパティであり、その値のnotです。実際の祝福された変数を別の変数に割り当てると、その祝福されていない浅いコピーが得られます。詳細については、 perlobj を参照してください。

64
Ilmari Karonen

エリックが言ったことに加えて、次のCコードを検討してください。

_void f(void* x);

f(42);
f("hello");
_

Python、C#などの言語とは対照的に、Javaまたはその他)、上記はlose型情報であるため弱い型付けです。エリックはC#でできることを正しく指摘しましたキャストしてコンパイラを回避し、効果的に「この変数の型についてはあなたよりも知っている」と伝えます。

しかし、それでも、ランタイムは型をチェックします!キャストが無効な場合、ランタイムシステムはキャストをキャッチし、例外をスローします。

型の消去では、これは起こりません。型情報は破棄されます。 Cの_void*_へのキャストは、まさにそれを行います。この点で、上記はvoid f(Object x)などのC#メソッド宣言と根本的に異なります。

(技術的には、C#は安全でないコードまたはマーシャリングによる型の消去も許可します。)

これは、それと同じくらい弱い型付けです。それ以外はすべて静的型チェックと動的型チェックの問題です。つまり、その時点でwhen型がチェックされます。

19
Konrad Rudolph

完璧な例は Strong Typingのウィキペディアの記事

一般に、強い型付けは、プログラミング言語が、発生が許可される混合に厳しい制限を課すことを意味します。

弱いタイピング

_a = 2
b = "2"

concatenate(a, b) # returns "22"
add(a, b) # returns 4
_

強い入力

_a = 2
b = "2"

concatenate(a, b) # Type Error
add(a, b) # Type Error
concatenate(str(a), b) #Returns "22"
add(a, int(b)) # Returns 4
_

弱い型付け言語は、エラーなしで異なる型を混合できることに注意してください。強い型言語では、入力型が期待される型である必要があります。強い型言語では、型を変換(str(a)整数を文字列に変換)またはキャスト(int(b))できます。

これはすべて、タイピングの解釈に依存します。

14
SaulBack

実際、弱いタイピングは、コーダーが意図したものを推測しようとして、高い割合のタイプを暗黙的に強制できることを意味します。

厳密な型指定とは、型が強制されないこと、または少なくとも強制が少なくなることを意味します。

静的型付けとは、コンパイル時に変数の型が決定されることを意味します。

最近、多くの人が「マニフェストリータイプ」と「ストロングタイプ」を混同しています。 「マニフェストリー型」とは、変数の型を明示的に宣言することを意味します。

Pythonは主に強く型付けされていますが、ブール型コンテキストではほとんど何でも使用でき、ブール型は整数コンテキストで使用でき、整数型はフロートコンテキストで使用できます。型を宣言する必要がないため、明示的に型指定されていません(ただし、Cythonは例外ですが、完全にPythonではありませんが、興味深いものです)。また、静的に型指定されていません。

CとC++は、型を宣言し、型をコンパイル時に決定し、整数とポインター、または整数と倍精度を組み合わせたり、ポインターを1つの型にキャストしたりできるため、明らかに型付け、静的型付け、やや強く型付けされています別の型へのポインタ。

Haskellは明確に型付けされていないため、興味深い例ですが、静的かつ強く型付けされています。

4
user1277476

他の人がコメントして貢献しているので、私は彼らの答えを読んで参考文献をたどっていて、興味深い情報を見つけたので、このテーマに関する私自身の研究で議論に貢献したいと思います。提案されているように、これは実際よりも理論的であると思われるため、このほとんどがプログラマーフォーラムでよりよく議論される可能性があります。

理論的な観点からは、Luca CardelliとPeter Wegnerによる 型の理解、データの抽象化、多態性について という記事は、私が読んだ最高の議論の1つだと思います。

タイプは、基礎となるntyped表現をarbitrary意的または意図的でない使用から保護する衣服のセット(または鎧のスーツ)と見なすことができます。基礎となる表現を隠し、オブジェクトが他のオブジェクトと相互作用する方法を制限する保護カバーを提供します。型付けされていないシステムでは、型付けされていないオブジェクトはnakedであり、すべての人に見えるように基礎となる表現が公開されています。型システムに違反すると、衣服の保護セットを取り外し、裸の表現で直接操作することが含まれます。

このステートメントは、弱いタイピングにより、タイプの内部構造にアクセスし、それが他のタイプ(別のタイプ)であるかのように操作できることを示唆しているようです。おそらく安全でないコード(Ericが言及)またはKonradが言及したc型消去されたポインターでできること。

記事は続きます...

すべての式がtype-consistentである言語は、厳密に型指定された言語と呼ばれます。言語が強く型付けされている場合、コンパイラは、受け入れるコンパイラが型エラーなしで実行されることを保証できます。一般に、厳密な型指定に努め、可能な限り静的型付けを採用する必要があります。静的に型付けされた言語はすべて強く型付けされますが、その逆は必ずしも当てはまりません。

このように、強い型付けは型エラーがないことを意味し、弱い型付けはその逆、つまり型エラーが存在する可能性が高いことを意味していると推測できます。実行時またはコンパイル時?ここでは無関係のようです。

おもしろいことに、この定義によると、Perlのような強力な型強制を持つ言語は、システムが失敗するわけではないので、強く型付けされていると見なされますが、適切かつ明確に定義された等価性に型を強制することで型を処理しています。

一方で、ClassCastExceptionおよびArrayStoreException(Javaの場合)およびInvalidCastExceptionArrayTypeMismatchException(C#の場合)の許容値は、弱い型付けのレベルを示すと言えますか、少なくともコンパイル時に?エリックの答えはこれに同意するようです。

この質問の回答の1つで提供されているリファレンスの1つで提供されている Typeful Programming という名前の2番目の記事では、Luca Cardelliが型違反の概念を詳しく調べています。

ほとんどのシステムプログラミング言語では、任意の型の違反が許可されます。一部は無差別に、一部はプログラムの制限された部分でのみ許可されます。型違反を伴う操作は、不健全と呼ばれます。型違反はいくつかのクラスに分類されます[中でも言及できます]:

基本値の強制:これらには、整数、ブール、文字、セットなどの間の変換が含まれます。実行するために組み込みのインターフェイスを提供できるため、ここで型違反の必要はありません。タイプサウンドの方法で強制を排除します。

そのため、演算子によって提供されるような型強制は型違反と見なすことができますが、型システムの一貫性を壊さない限り、弱い型付けシステムにつながるとは言えません。

これに基づいて、Python、Perl、JavaまたはC#は弱く型付けされません。

Cardelliは、2つのタイプの違反について言及しています。

アドレス演算。必要に応じて、組み込みの(不健全な)インターフェースがあり、アドレスと型変換に関する適切な操作を提供します。さまざまな状況には、ヒープへのポインター(コレクターの再配置では非常に危険です)、スタックへのポインター、静的領域へのポインター、および他のアドレス空間へのポインターが含まれます。場合によっては、配列のインデックス付けがアドレス演算を置き換えることがあります。 メモリマッピング。これには、メモリの領域を非構造化配列と見なしますが、構造化データが含まれます。これは典型的なメモリアロケーターとコレクターです。

Cのような言語(Konradが言及)または.Netの安全でないコード(Ericが言及)で可能なこの種のことは、本当にタイピングが弱いことを意味します。

この概念の定義は非常に理論的であり、特定の言語に関しては、これらすべての概念の解釈が異なる議論の余地のある結論につながる可能性があるため、これまでの最善の答えはエリックのものだと思います。

4
Edwin Dalorzo

強い<=>弱いタイピングは、あるデータ型の言語によって自動的に強制される値の量または量の連続性だけでなく、実際のの強さまたは弱さに関するものです。値が入力されます。 PythonおよびJava、およびほとんどの場合C#では、値のタイプは石に設定されています。 Perlでは、それほど多くはありません-実際には、変数に格納する異なる値型はほんの一握りしかありません。

ケースを1つずつ開きましょう。


Python

Pythonの例1 + "1"+演算子では、int型の__add__を呼び出して、引数として文字列"1"を指定しますが、これはNotImplementedになります:

>>> (1).__add__('1')
NotImplemented

次に、インタープリターはstrの__radd__を試行します。

>>> '1'.__radd__(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'str' object has no attribute '__radd__'

失敗すると、+演算子は失敗し、結果TypeError: unsupported operand type(s) for +: 'int' and 'str'が返されます。そのため、例外は厳密な型指定についてはあまり言及していませんが、演算子+は引数を自動的に強制しません。同じ型は、Pythonが連続体の中で最も弱い型付けの言語ではないという事実へのポインターです。

一方、Python 'a' * 5isで実装されています:

>>> 'a' * 5
'aaaaa'

あれは、

>>> 'a'.__mul__(5)
'aaaaa'

操作が異なるという事実には、いくつかの強力な型付けが必要です-ただし、*の反対の値を乗算する前に値を強制することは、必ずしも値を弱く型付けするとは限りません。


Java

Javaの例、String result = "1" + 1;が機能するのは、便宜上、演算子+が文字列に対してオーバーロードされているためです。 Java +演算子は、シーケンスをStringBuilderの作成に置き換えます( this を参照):

String result = a + b;
// becomes something like
String result = new StringBuilder().append(a).append(b).toString()

これは、実際の強制なしの非常に静的な型付けの例です。StringBuilderには、ここで特に使用されるメソッド append(Object) があります。ドキュメントには次のことが記載されています。

Object引数の文字列表現を追加します。

全体的な効果は、メソッドString.valueOf(Object)によって引数が文字列に変換され、その文字列の文字がこの文字シーケンスに追加された場合とまったく同じです。

ここで String.valueOf then

Object引数の文字列表現を返します。 [戻り値]引数がnullの場合、"null"に等しい文字列。それ以外の場合、obj.toString()の値が返されます。

したがって、これは言語による強制がまったくない場合であり、すべての懸念をオブジェクト自体に委任します。


C#

Jon Skeetの回答 によると、演算子+stringクラスに対してもオーバーロードされていません-Javaと同様、これはコンパイラーによって生成された便利さです。静的型と強力型の両方に感謝します。


Perl

perldata が説明しているように、

Perlには、スカラー、スカラーの配列、および「ハッシュ」として知られる連想配列のスカラーの3つの組み込みデータ型があります。スカラーは、単一の文字列(任意のサイズで、使用可能なメモリによってのみ制限されます)、数値、または何かへの参照(perlrefで説明します)です。通常の配列は、0から始まる番号でインデックス付けされたスカラーの順序付きリストです。ハッシュは、関連付けられた文字列キーでインデックス付けされたスカラー値の順序付けられていないコレクションです。

ただし、Perlには、数値、ブール値、文字列、null、undefineds、他のオブジェクトへの参照などの個別のデータ型はありません。これらすべてに対してスカラー型が1つだけあります。 0は、「0」と同じスカラー値です。文字列として設定されたスカラー variable は実際に数値に変更でき、そこからアクセスされる場合、「単なる文字列」とは異なる動作をします 数値コンテキスト 。スカラーはPerlで何でも保持でき、システムに存在するオブジェクトと同じです。 Pythonでは名前は単にオブジェクトを指しますが、Perlでは名前のスカラー値は変更可能なオブジェクトです。さらに、オブジェクト指向型システムはこの上に接着されています。Perlにはスカラー、リスト、ハッシュという3つのデータ型しかありません。 Perlのユーザー定義オブジェクトは、パッケージへのblessedの参照(前述の3つのいずれかへのポインター)です。このような値を取得し、いつでも任意のクラスに祝福できます。

Perlは値のクラスを気まぐれに変更することもできます-これはPythonでは不可能です。あるクラスの値を作成する場合、object.__new__でそのクラスに属する値を明示的に構築する必要がありますまたは類似。 Pythonでは、作成後にオブジェクトの本質を実際に変更することはできません。Perlでは、多くのことができます。

package Foo;
package Bar;

my $val = 42;
# $val is now a scalar value set from double
bless \$val, Foo;
# all references to $val now belong to class Foo
my $obj = \$val;
# now $obj refers to the SV stored in $val
# thus this prints: Foo=SCALAR(0x1c7d8c8)
print \$val, "\n"; 
# all references to $val now belong to class Bar
bless \$val, Bar;
# thus this prints Bar=SCALAR(0x1c7d8c8)
print \$val, "\n";
# we change the value stored in $val from number to a string
$val = 'abc';
# yet still the SV is blessed: Bar=SCALAR(0x1c7d8c8)
print \$val, "\n";
# and on the course, the $obj now refers to a "Bar" even though
# at the time of copying it did refer to a "Foo".
print $obj, "\n";

したがって、タイプアイデンティティは変数に弱くバインドされ、オンザフライで参照を介して変更できます。実際、そうするなら

my $another = $val;

\$anotherは依然として祝福された参照を提供しますが、\$valにはクラスIDがありません。


TL; DR

Perlに対する弱い型付けは、単なる自動強制よりもはるかに多く、動的に非常に強く型付けされたPythonとは異なり、値の型自体が明確に設定されていないことです。 pythonが1 + "1"TypeErrorを与えることは、JavaまたはC#は、厳密に型指定された言語であることを排除しません。

3
Antti Haapala

他の多くの人が表明しているように、「強い」タイピングと「弱い」タイピングの概念全体に問題があります。

原型として、Smalltalkは非常に強く型付けされています-2つのオブジェクト間の操作に互換性がない場合、常にalways例外が発生します。ただし、Smalltalkは動的に型付けされているため、このリストのほとんどが厳密に型付けされた言語であるとは思わないでしょう。

「静的」と「動的」タイピングの概念は、「強い」と「弱い」よりも便利だと思います。静的に型付けされた言語は、コンパイル時にすべての型を計算し、プログラマはそうでない場合は明示的に宣言する必要があります。

実行時にタイピングが実行される動的に型付けされた言語とは対照的です。これは通常、ポリモーフィック言語の要件であるため、2つのオブジェクト間の操作が合法かどうかの決定は、プログラマーが事前に決定する必要はありません。

ポリモーフィックで動的に型付けされた言語(SmalltalkやRubyなど)では、「型」を「プロトコルへの適合性」と考える方が便利です。オブジェクトが別のオブジェクトと同じ方法でプロトコルに準拠している場合-2つのオブジェクトが継承、ミックスイン、または他のブードゥーを共有していない場合でも-ランタイムシステムでは同じ「タイプ」と見なされます。より正確には、そのようなシステムのオブジェクトは自律的であり、特定の引数を参照する特定のメッセージに応答することが理にかなっているかどうかを判断できます。

青を説明するオブジェクト引数を使用して、メッセージ「+」に対して何らかの意味のある応答を行うことができるオブジェクトが必要ですか?動的に型付けされた言語でそれを行うことができますが、静的に型付けされた言語では苦痛です。

0
Jan Steinman

@ Eric Lippert's answer が好きですが、質問に対処するために、強く型付けされた言語は通常、プログラムの各ポイントで変数のタイプを明示的に知っています。弱い型付けの言語はそうではないため、特定の型では不可能な操作を実行しようとする可能性があります。これを確認する最も簡単な方法は、関数の中にあると思います。 C++:

void func(string a) {...}

変数aは文字列型であることが知られており、互換性のない操作はコンパイル時にキャッチされます。

Python:

def func(a)
  ...

変数aは何でもかまいませんし、実行時にのみキャッチされる無効なメソッドを呼び出すコードを持つことができます。

0
Lubo Antonov