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





強い型付け 型推論 (9)


更新: この質問は2012年10月15日のブログの主題でした。偉大な質問をありがとう!

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

これは、「この言語は、私が不愉快だと感じるタイプシステムを使用している」という意味です。 対照的に「強く型付けされた」言語は、私が楽しいと感じる型システムを持つ言語です。

これらの用語は本質的に無意味であり、それらを避けるべきです。 Wikipediaは、「強く型付けされた」という意味の11の異なる意味が列挙されており、そのうちいくつかは矛盾している。 これは、「強く型付けされた」または「弱く型付けされた」という用語を含む会話では、混乱の蓋然性が高いことを示しています。

議論中の「強く型付けされた」言語では、実行時やコンパイル時に型システムにいくつかの追加の制限があり、議論中の「弱く型付けされた」言語が欠けているということです。 その制限は、それ以上の文脈なしには決めることができません。

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

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

それは本当に何ですか? 言うことは不可能です。 それは、話し手の視点および様々な言語機能に対する彼らの態度に依存する。

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

たとえば、「 Typing:Strong vs. Weak 」というこの記事では、静的対動的の場合、次のようにすると例外が発生するため、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やC#は作成されません。

さあ、これは混乱している

著者は、異なるタイプの値に対する特定の演算の適用を妨げる言語が強く型付けされ、逆に弱い型付けが行われることを意味するように思われる。

したがって、いくつかの点では、ある言語が多くの自動変換やタイプ間強制(perlのように)が弱い型指定とみなされる可能性がありますが、わずかな変換しか提供しない他の言語は終了する可能性がある強く型付けされていると考えられる

私はこのinterepretationで間違っている必要がありますが、私は信じる傾向がある、私はちょうどそれを説明する理由や方法を知らない。

ですから、私の質問は次のとおりです:

  • 本当に弱く型付けされた言語が本当に意味することは何ですか?
  • 言語による自動変換/自動強制変換に関係しない、弱い型の良い例を挙げてください。
  • 言語を弱く型付けして強く型付けすることはできますか?



強い<=>弱いタイピングは、あるデータ型の言語によって自動的に値がどれくらいか、どれくらい自動的に強制されるかについての連続性だけでなく、実際のがどれだけ強くまたは弱くタイプされているかに関する連続性に関するものです。 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' * 5 実装されています:

>>> '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は数値、ブール値、文字列、ヌル、 undefined 、他のオブジェクトへの参照などのための別個のデータ型を持っていません。 0はスカラー値であり、「0」である。 文字列として設定されたスカラー変数は実際には数値に変更でき、そこから数値的コンテキストでアクセスされる場合は "文字列" とは異なる動作をします 。 スカラーはPerlで何かを保持できますが、それはシステムに存在するオブジェクトと同じくらいです。 Pythonでは名前がオブジェクトを参照するだけで、Perlでは名前のスカラー値は変更可能なオブジェクトです。 さらに、オブジェクト指向型システムは、この上に貼り付けられています。パーラルスカラー、リスト、ハッシュには3つのデータ型しかありません。 Perlのユーザ定義オブジェクトは、パッケージにblessされた参照(これは前の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は依然として祝福された参照を与えますが、 \$anotherはクラスIDを持ちません。

TL; DR

自動強制変換だけでなく、Perlに対する微妙な型付けについてはずっと多くのことがあります。動的でありながら非常に厳密に型付けされた言語であるPythonとは異なり、値自体の型は石に設定されていません。 PythonがTypeError1 + "1"与えているのは、JavaやC#のように有用なことをしているのとは逆のことが強く型付けされた言語であることを排除しないとしても、




@Eric Lippertの答えが気に入っていますが、強く型付けされた言語は、通常、プログラムの各ポイントでの変数の種類を明示的に知っています。弱く型付けされた言語はそうではないので、特定の型に対しては不可能な操作を実行しようとする可能性があります。これを見る最も簡単な方法は関数にあると考えています。C ++:

void func(string a) {...}

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

Python:

def func(a)
  ...

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




弱い型付けは実際には、型の高いパーセンテージが暗黙的に強制され、コーダが意図したものを推測しようとしていることを意味します。

厳密な型定義とは、型が強制されないか、少なくとも型どりが少ないことを意味します。

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

多くの人々は最近、「明示的に型指定された」と「強く型付けされた」型を混同しています。 「マニフェスト型」とは、変数の型を明示的に宣言することを意味します。

Pythonは大部分が強く型付けされていますが、ブール値のコンテキストではほとんど何でも使用できますが、ブール値は整数コンテキストで使用でき、整数は浮動小数点コンテキストで使用できます。 あなたの型を宣言する必要はないので、明らかに型付けされていません(Cythonは例外ではありませんが、完全にはPythonではありません)。 また、静的に型指定されていません。

型を宣言したり、型をコンパイル時に決定したり、整数とポインタ、整数と倍精度を混在させたり、ある種の型へのポインタをキャストすることができるため、CやC ++は明示的に型指定され、静的に型付けされています。別の型へのポインタ。

Haskellは明らかにタイプされていないので興味深い例ですが、静的で強く型付けされています。




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

理論的な見地から、Luca CardelliとPeter WegnerのUnderstanding Types、Data Abstraction and Polymorphismの記事には、私が読んだ最良の議論の1つがあると思います。

タイプは、基本的なタイプのない表現を恣意的または意図しない使用から保護する一連の服(または鎧のスーツ)として見ることができます。 それは、基礎となる表現を隠し、オブジェクトが他のオブジェクトとやり取りする方法を制限する保護カバーを提供します。 型指定されていないシステムでは、型が指定されていないオブジェクトは、根底にある表現がすべてが見ることができるように公開されています。 タイプシステムに違反することは、衣服の保護セットを取り除き、裸の表現で直接操作することを含む。

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

記事は続きます...

すべての式の型が一致する言語は、厳密に型指定された言語と呼ばれます。 言語が強く型付けされている場合、コンパイラは受け入れるプログラムが型エラーなしで実行されることを保証することができます。 一般的に、私たちは強力な型付けに努め、できるだけ静的型付けを採用するべきです。 すべての静的型付き言語は強く型付けされていますが、その逆は必ずしも真ではないことに注意してください。

このように、強い型指定は型エラーが存在しないことを意味し、弱い型定義は逆のことを意味します。型エラーの存在が考えられます。 実行時またはコンパイル時に? ここでは無関係です。

面白いことに、Perlのような強力な型強制を持つ言語は、システムが失敗していないため強く型付けされていると見なされますが、型を適切かつ明確な同値に強制することによって型を処理しています。

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

Luca Cardelliは、この質問の回答の1つに記載されている参考文献の1つであるTypeful Programmingという2番目の記事で、タイプ違反の概念を掘り下げています。

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

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

したがって、演算子によって提供されるような型強制は型違反とみなされる可能性がありますが、型システムの一貫性を破らない限り、弱い型付きシステムにはならないと言えるかもしれません。

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

Cardelliは、私が本当に弱いタイピングの事例を非常によく考慮する2つのタイプの批判について言及しています。

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

Cのような言語(Konradで言及されている)や.Netの安全でないコード(Ericで言及されている)のような言語で可能なこの種のものは、弱いタイピングを真に意味するでしょう。

これまでのところ、エリックはこの概念の定義が非常に理論的であり、特定の言語になると、これらすべての概念の解釈が異なる議論の可能な結論につながる可能性があると私は信じています。




他者が指摘しているように、「強く型付けされた」および「弱く型付けされた」という用語は、あなたの質問に対する答えが一つもないほど多くの異なる意味を持っています。 しかし、Perlがあなたの質問に具体的に言及して以来、私はPerlがどのような意味で弱く型付けされているのかを説明しようとします。

要点は、Perlでは、「整数変数」、「float変数」、「文字列変数」、「ブール変数」などは存在しないということです。 実際、ユーザーが(通常)伝えることができる限り、整数、浮動小数点数、文字列またはブールもありません。あなたが持っているのはすべて「スカラー」です。これらはすべて同時にこれらのものです。 例えば、次のように書くことができます:

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

もちろん、あなたが正しく注記しているように、これはすべてタイプ強制とみなすことができます。 しかし、ポイントは、Perlでは、型は常に強制されるということです。 実際には、変数の内部 "タイプ"が何であるかをユーザが知ることは非常に難しいです:上記の例の2行目で、 $barの値が文字列"9"か数字9がかわいいかを尋ねる多くの意味がない。なぜなら、Perlが関係する限り、 それらは同じことだからである 。 確かに、Perlスカラーが内部的に文字列と数値の両方を同時に持つことさえ可能です。例えば、上記の2行目以降の$fooの場合です。

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

たとえば、Perlは数値加算演算子( + )と文字列連結演算子( . )の両方を必要とします:上で見たように、文字列( "1" + "2" == "3" )または数字を連結する( 1 . 2 == 12 )。 同様に、数値比較演算子==!=<><=>=および<=>は引数の数値を比較し、文字列比較演算子eqneltgtlegeおよびcmpはそれらを辞書的に文字列として比較します。 したがって、 2 < 10 2 gt 10 (ただし、 "02" lt 10"02" == 2 )。 (JavaScriptのような他の言語では、Perlのような微妙な型指定に対処すると同時にオペレータのオーバーロードが発生することに注意してください。これは+の連想性の喪失のような醜さにつながります)。

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

言われたすべては、Perl 強力な型を持っていると主張することができます。 あなたが期待している種類のものではありません。 具体的には、前述の「スカラー型」に加えて、「配列」と「ハッシュ」の2つの構造化型もあります。 それらはスカラーとは非常に異なっており、Perl変数の型が異なることを示しています(スカラの場合は$ 、配列の場合は@ 、ハッシュの場合は% )。 これらの型の間に強制規則があるので、例えば%foo = @barと書くことができます 、それらの多くはかなり損失があります:例えば、 $foo = @barは、配列@bar 長さを内容ではなく$fooに割り当てます。 (また、タイプグロブやI / Oハンドルのように、よく見かけることのない奇妙なタイプがいくつかあります。)

また、この素晴らしいデザインのちょっとした欠点は、特別な種類のスカラーである参照型が存在することです(また、 ref演算子を使用して通常のスカラーと区別できます)。 参照を通常のスカラとして使用することは可能ですが、文字列/数値は特に有用ではありません。通常のスカラ操作を使用して参照を変更すると、特殊な参照参照が失われがちです。 また、Perl変数2はクラスにブレスされ、そのクラスのオブジェクトに変換されます。 PerlのOOクラスシステムは、前述のプリミティブ型(または型なし)システムとやや直交していますが、 ダック型のパラダイムに従うという意味では「弱」です。 一般的な見解は、Perlでオブジェクトのクラスをチェックしていると、何か間違っているということです。

1実際には、sigilはアクセスされる値の型を表します。たとえば、配列@foo最初のスカラーは$foo[0]ます。 詳細はperlfaq4を参照してください。

2 Perlのオブジェクトは、(通常は)それらの参照によってアクセスされますが、実際にはblessは、参照が指し示す(おそらく匿名の)変数です。 しかし、実際の祝福された変数を別のものに割り当てると、祝福​​はそれほど浅く、祝福されていないコピーになります。 詳細はperlobjを参照してください。




Ericの言ったことに加えて、次のCコードを考えてみましょう。

void f(void* x);

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

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

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

タイプ消去では、これは起こりません - タイプ情報は投げ捨てられます。 Cのvoid*へのキャストはまさにそれを行います。 これに関して、上記は基本的にvoid f(Object x)などのC#メソッド宣言とは異なります。

(技術的には、C#は安全でないコードやマーシャリングによる型の削除も可能です。)

これは弱い型付けされたものです。 それ以外のものは静的対動的型チェック、つまり型がチェックされた時の問題です。




他の多くの人が表明しているように、 "強い"対 "弱い"タイピングの全体の概念は問題が多い。

原型として、Smalltalkは非常に強く型付けされています.2つのオブジェクト間の操作が互換性がない場合、常に例外が発生します。しかし、このリストでは、動的に型付けされているため、Smalltalkを強く型付けされた言語と呼ぶことはほとんどありません

私は、「静的」対「動的」の入力が「強」対「弱」よりも有用であると考えています。静的型付き言語はコンパイル時にすべての型を把握しており、プログラマはそれ以外の場合は明示的に宣言しなければなりません。

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

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

色の青を表すオブジェクト引数でメッセージ "+"に意味のある応答をすることができるオブジェクトが必要ですか?これは動的に型指定された言語で行うことができますが、静的型言語では苦労します。










c# java python perl weakly-typed