動的型付けではどのような機能が許可されますか?[閉まっている]


91

私は数日前からPythonを使用していますが、動的型付けと静的型付けの違いを理解していると思います。私が理解していないのは、どのような状況下でそれが好ましいかということです。柔軟性があり読みやすいですが、実行時のチェックが多くなり、ユニットテストが必要になります。

柔軟性や読みやすさなどの非機能的な基準とは別に、動的型付けを選択する理由は何ですか?そうでなければ不可能な動的型付けで何ができますか?動的型付けの具体的な利点を示す具体的なコード例は何ですか?


5
理論的には、言語がチューリング完全である限り、どちらにもできないことはありません。私にとってより興味深い質問は、どちらが簡単か自然かです。Pythonで私が定期的にやっていることは、C ++では可能だとは思いますが、C ++でさえ考慮しないことです。
マークランサム

28
クリス・スミスが優れたエッセイで書いているように、型システムを議論する前に知っておくべきこと:「この場合の問題は、ほとんどのプログラマーが経験が限られており、多くの言語を試したことがないことです。ここでは、6、7これは、2つの興味深い結果です。(1)多くのプログラマーは、非常に貧弱な静的型付け言語を使用しました。(2)多くのプログラマーは、動的型付け言語を非常に不十分に使用しました。
ダニエル・プライデン

3
@suslik:言語プリミティブに無意味な型がある場合、もちろん無意味なことを型で行うことができます。これは、静的タイピングと動的タイピングの違いとは関係ありません。
ジョンパーディ

10
@CzarekTomczak:それはいくつかの動的型付け言語の機能です、はい。ただし、静的に型指定された言語を実行時に変更することは可能です。たとえば、Visual Studioでは、デバッガーのブレークポイントにいる間にC#コードを書き換えたり、命令ポインターを巻き戻して新しい変更を加えてコードを再実行したりすることができます。私が他のコメントでクリス・スミスを引用したように、「多くのプログラマーが非常に貧弱な静的型付け言語を使用している」-静的型付け言語をすべてあなたが知っている言語で判断しないでください。
ダニエル・プライデン

11
@WarrenP:「動的な型システムにより、入力しなければならない余分なデータの量が減る」と断言しますが、PythonとC ++を比較します。これは公平な比較ではありません。もちろん、C ++はPythonよりも冗長ですが、それは型システムの違いによるものではなく、文法の違いによるものです。プログラムソースの文字数を減らしたい場合は、JまたはAPLを学んでください。短くすることを保証します。より公平な比較は、PythonとHaskellを比較することです。(記録のために:私はPythonが好きで、C ++よりもPythonが好きですが、Haskellはさらに好きです。)
ダニエルプライデン

回答:


50

あなたは特定の例を求めたので、私はあなたにそれをあげます。

Rob ConeryのMassive ORMは400行のコードです。Robは、SQLテーブルをミラーリングするために多くの静的型を必要とせずに、SQLテーブルをマップし、オブジェクトの結果を提供できるため、これほど小さいです。これはdynamic、C#のデータ型を使用して実現されます。RobのWebページでは、このプロセスについて詳しく説明していますが、この特定のユースケースでは、動的型付けがコードの簡潔さの大部分を担っていることは明らかです。

静的型を使用するSam SaffronのDapperと比較してください。SQLMapper単独クラスは、コードの3000行です。

通常の免責事項が適用されることに注意してください。また、走行距離は異なる場合があります。DapperにはMassiveとは異なる目標があります。これは、おそらく動的なタイピングなしでは不可能な400行のコードでできることの例として指摘しています。


動的型付けを使用すると、型の決定を実行時まで延期できます それで全部です。

動的に型付けされた言語を使用する場合も、静的に型付けされた言語を使用する場合も、型の選択は賢明でなければなりません。文字列に数値データが含まれていない限り、2つの文字列を一緒に追加して数値の回答を期待することはありません。含まれない場合は、予期しない結果が得られます。静的に型付けされた言語では、そもそもこれを行うことはできません。

静的型言語の支持者は、コンパイラーが、コンパイル時に、単一行が実行される前に、かなりの量のコードを「健全性検査」できると指摘しています。これはGood Thing™です。

C#にはdynamicキーワードがあり、これにより、コードの残りの部分で静的型安全性の利点を失うことなく、型決定を実行時まで延期できます。型推論(var)は、型を常に明示的に宣言する必要性をなくすことにより、静的に型付けされた言語で書く苦労の多くを取り除きます。


動的言語は、プログラミングに対するよりインタラクティブで即時的なアプローチを好むようです。クラスを作成し、コンパイルサイクルを経てLispコードを少し入力して実行を監視する必要があるとは誰も期待していません。しかし、それはまさに私がC#で行うことを期待していることです。


22
2つの数値文字列を一緒に追加しても、数値結果は期待できません。
pdr

22
@Robert私はあなたの答えのほとんどに同意します。ただし、ScalaやHaskellなど、対話型の読み取り-評価-印刷ループを持つ静的に型付けされた言語があることに注意してください。C#は特にインタラクティブな言語ではないかもしれません。
アンドレスF.

14
Haskellを学ぼう。
ロバートハーベイ

7
@RobertHarvey:まだ試していないのであれば、F#に驚いたり感動したりするかもしれません。通常、.NET言語で得られる(コンパイル時の)型の安全性はすべて得られますが、型を宣言する必要はほとんどありません。F#の型推論は、C#で利用できる/動作するものを超えています。また:アンドレスとダニエルは... F#インタラクティブは、Visual Studioの一部であり、指摘されているものと同様
スティーブン・エヴァース

8
「あなたが一緒に2つの文字列を追加し、文字列が数値データが含まれていない限り、数値の答えを期待するつもりはない、とそうでない場合は、あなたが予期しない結果を取得するつもりだ」申し訳ありませんが、これはとは何の関係もありません静的対動的な型付けを、これは強いタイプと弱いタイプです。
バルテック

26

「静的な型付け」や「動的な型付け」などのフレーズが頻繁に使われ、人々は微妙に異なる定義を使用する傾向があるため、まずは意味を明確にすることから始めましょう。

コンパイル時にチェックされる静的型を持つ言語を考えてください。しかし、型エラーは致命的でない警告のみを生成し、実行時にはすべてがダック型であると言います。これらの静的型はプログラマーの便宜のためだけのものであり、コード生成には影響しません。これは、静的型付け自体が制限を課すものではなく、動的型付けと相互排他的ではないことを示しています。(Objective-Cはこのようなものです。)

しかし、ほとんどの静的型システムはこのように動作しません。制限を課すことができる静的型システムの2つの一般的なプロパティがあります。

コンパイラは、静的型エラーを含むプログラムを拒否する場合があります。

多くのタイプセーフプログラムには必ず静的タイプエラーが含まれるため、これは制限です。

たとえば、Python 2とPython 3の両方として実行する必要があるPythonスクリプトがあります。一部の関数は、Python 2と3の間でパラメータータイプを変更したため、次のようなコードがあります。

if sys.version_info[0] == 2:
    wfile.write(txt)
else:
    wfile.write(bytes(txt, 'utf-8'))

Python 2静的型チェッカーは、実行されない場合でもPython 3コードを拒否します(逆も同様です)。タイプセーフプログラムに静的タイプエラーが含まれています。

別の例として、OS X 10.6で実行したいが、10.7の新機能を利用したいMacプログラムを考えてみましょう。10.7メソッドは実行時に存在する場合と存在しない場合があり、それらを検出するのはプログラマーです。静的型チェッカーは、型安全性を確保するためにプログラムを拒否するか、実行時に型エラー(関数の欠落)を生成する可能性があるプログラムを受け入れるように強制されます。

静的型チェックは、ランタイム環境がコンパイル時情報によって適切に記述されていることを前提としています。しかし、未来を予測することは危険です!

もう1つの制限があります。

コンパイラーは、ランタイム型が静的型であると想定するコードを生成する場合があります。

静的型が「正しい」と仮定すると、最適化の機会が多く提供されますが、これらの最適化は制限される場合があります。良い例は、リモートオブジェクトなどのプロキシオブジェクトです。メソッド呼び出しを別のプロセスの実際のオブジェクトに転送するローカルプロキシオブジェクトが必要だとします。プロキシが汎用(オブジェクトのようにマスカレードできる)で透過的(既存のコードがプロキシと通信していることを知る必要がないように)できれば便利です。しかし、これを行うには、オブジェクトが実際にプロキシである場合に失敗するため、メソッド呼び出しを静的にインライン化するなどして、静的型が正しいと仮定するコードをコンパイラが生成できません。

そのようなリモート処理の例には、ObjCのNSXPCConnectionまたはC#のTransparentProxy(ランタイムでいくつかのペシミゼーションが必要でした- 議論についてはこちらを参照)が含まれます。

codegenが静的タイプに依存せず、メッセージ転送などの機能がある場合、プロキシオブジェクトやデバッグなどを使用して多くのクールなことができます。

したがって、これは、型チェッカーを満たす必要がない場合にできることの一部のサンプリングです。制限は静的型によって課せられるのではなく、強制的な静的型チェックによって課されます。


2
「Python 2静的型チェッカーは、実行されない場合でもPython 3コードを拒否します(逆も同様です)。私のタイプセーフプログラムには静的型エラーが含まれています。」本当に必要なのは、ある種の「静的if」があり、条件がfalseの場合にコンパイラ/インタープリターがコードを認識しないことです。
デビッドストーン

@davidstone c ++に存在する
Milind R 14

A Python 2 static type checker would reject the Python 3 code (and vice versa), even though it would never be executed. My type safe program contains a static type error. 妥当な静的言語であればIFDEF、どちらの場合も型の安全性を維持しながら、型プリプロセッサステートメントでこれを行うことができます。
メイソンウィーラー14

1
@ MasonWheeler、davidstoneいいえ、プリプロセッサトリックとstatic_ifは両方とも静的すぎます。私の例ではPython2とPython3を使用しましたが、バージョン間でインターフェースが変更されたAmazingModule2.0とAmazingModule3.0のように簡単にできます。インターフェースを知ることができる最も早い時期は、モジュールのインポート時です。これは必ず実行時です(少なくとも動的リンクをサポートしたい場合)。
ridiculous_fish 14

18

ダック型変数は誰もが最初に考えるものですが、ほとんどの場合、静的型推論を通じて同じ利点を得ることができます。

しかし、動的に作成されたコレクションでのダックタイピングは、他の方法では実現が困難です。

>>> d = JSON.parse(foo)
>>> d['bar'][3]
12
>>> d['baz']['qux']
'quux'

では、どの型がJSON.parse返されますか?整数の配列または文字列の辞書の辞書?いいえ、それでも十分ではありません。

JSON.parsenull、bool、float、string、これらの型の配列を再帰的に、または文字列からこれらの型のいずれかを再帰的に辞書に格納できる「バリアント値」を返す必要があります。動的型付けの主な長所は、そのようなバリアント型を持つことです。

これまでのところ、これは動的型言語ではなく、動的型の利点です。適切な静的言語は、そのような型を完全にシミュレートできます。(そして、「悪い」言語でさえ、ボンネットの下で型安全性を破る、および/または不器用なアクセス構文を要求することによって、しばしばそれらをシミュレートできます。)

動的に型付けされた言語の利点は、静的型推論システムによってそのような型を推論できないことです。型を明示的に記述する必要があります。しかし、このような1回を含む多くの場合、型を記述するコードは、型を記述せずにオブジェクトを解析/構築するコードとまったく同じくらい複雑であるため、必ずしも利点ではありません。


21
JSON解析の例は、代数データ型によって静的に簡単に処理できます。

2
OK、私の答えは十分に明確ではありませんでした。ありがとう。そのJSValueは動的型の明示的な定義であり、まさに私が話していたものです。有用なのは動的型であり、動的型付けを必要とする言語ではありません。ただし、実際の型推論システムによって動的型を自動的に生成できないことは依然として重要ですが、一般的な例のほとんどは人が自明であると推測できます。新しいバージョンがそれをより良く説明することを願っています。
アバーナート

4
@MattFenwick代数データ型は、関数型言語にほとんど制限されています(実際)。Javaやc#などの言語はどうですか?
spirc

4
ADTは、タグ付きユニオンとしてC / C ++に存在します。これは関数型言語に固有のものではありません。
クラークゲーベル

2
@spircは、共通のインターフェイス、getClass()またはGetType()へのランタイム呼び出し、および等価性チェックからすべて派生する複数のクラスを使用して、古典的なOO言語でADTをエミュレートできます。または、ダブルディスパッチを使用することもできますが、C ++の方が効果があると思います。そのため、JSObjectインターフェースと、JSString、JSNumber、JSHash、およびJSArrayクラスがあります。次に、この「型なし」データ構造を「アプリケーション型」データ構造に変換するコードが必要になります。しかし、おそらく動的に型付けされた言語でもこれを行いたいと思うでしょう。
ダニエルヤンコフスキー

12

すべてのリモートで実用的な静的型システムは、関係するプログラミング言語に比べて非常に制限されているため、実行時にコードがチェックできるすべての不変式を表現することはできません。型システムが提供しようとする保証を回避しないために、保守的なものを選択し、これらのチェックに合格するが、型システムでは証明できないユースケースを許可しないことを選択します。

例を挙げましょう。モデルがxFoo型のオブジェクトの属性が整数を保持していると言う場合、モデルは常に整数を保持する必要があるという意味で静的に型指定されたデータオブジェクト、それらのコレクションなどを記述する単純なデータモデルを実装するとします。これはランタイム構造であるため、静的に入力することはできません。YAMLファイルに記述されたデータを保存するとします。ハッシュマップ(後でYAMLライブラリに渡される)をx作成し、属性を取得し、マップに保存し、偶然にも文字列である他の属性を取得します。今のタイプは何the_map[some_key]ですか?よく、私たちはそれsome_keyがわかっている'x'ので、結果は整数でなければなりませんが、型システムはこれについて推論することさえできません。

活発に研究されている型システムの中には、この特定の例で機能するものもありますが、これらは非常に複雑です(コンパイラライターの実装とプログラマの推論の両方)、特にこの「単純な」何か(つまり、段落)。

もちろん、今日の解決策はすべてをボックス化してからキャストすることです(または、多数のオーバーライドされたメソッドを持ち、そのほとんどが「実装されていない」例外を発生させます)。しかし、これは静的に型付けされるのではなく、実行時に型チェックを行う型システムのハックです。


ジェネリック型にはボクシングの要件はありません。
ロバートハーベイ

@RobertHarveyはい。私はJava C#のボクシングについて話しいませんでした。「UのサブタイプでTの値を表す唯一の目的を持つラッパークラスでラップする」ことについて話していました。しかし、パラメトリック多相性(一般的なタイピングと呼ばれるもの)は私の例には当てはまりません。具体的な型に対するコンパイル時の抽象化ですが、実行時の型指定メカニズムが必要です。

Scalaの型システムはチューリング完全であることを指摘する価値があるかもしれません。そのため、型システムは、想像するほど簡単ではありません。
アンドレア

@Andrea私は意図的に説明を完全なものにすることはしませんでした。チューリングターピットでプログラムしたことがありますか?または、これらのものを型にエンコードしようとしましたか?ある時点で、実行するには複雑すぎます。

@delnan同意します。型システムがかなり複雑なことを行えることを指摘していました。あなたの答えは、型システムは些細な検証しかできないという意味でしたが、2回目の読み取りでは、このようなことは書きませんでしたという印象を受けました!
アンドレア

7

静的に型付けされた言語の上に動的な型付けを実装できるため、静的な型付けではできない動的な型付けでできることは何もありません。

Haskellの短い例:

data Data = DString String | DInt Int | DDouble Double

-- defining a '+' operator here, with explicit promotion behavior
DString a + DString b = DString (a ++ b)
DString a + DInt b = DString (a ++ show b)
DString a + DDouble b = DString (a ++ show b)
DInt a + DString b = DString (show a ++ b)
DInt a + DInt b = DInt (a + b)
DInt a + DDouble b = DDouble (fromIntegral a + b)
DDouble a + DString b = DString (show a ++ b)
DDouble a + DInt b = DDouble (a + fromIntegral b)
DDouble a + DDouble b = DDouble (a + b)

十分なケースがあれば、任意の動的型システムを実装できます。

逆に、静的に型付けされたプログラムを同等の動的なプログラムに変換することもできます。もちろん、静的に型付けされた言語が提供する正確性のコンパイル時の保証はすべて失われます。

編集:これをシンプルにしたかったのですが、オブジェクトモデルの詳細を以下に示します

関数はデータのリストを引数として受け取り、ImplMonadで副作用を伴う計算を実行し、データを返します。

type Function = [Data] -> ImplMonad Data

DMember メンバー値または関数のいずれかです。

data DMember = DMemValue Data | DMemFunction Function

Dataオブジェクトと関数を含めるように拡張します。オブジェクトは、名前付きメンバーのリストです。

data Data = .... | DObject [(String, DMember)] | DFunction Function

これらの静的な型は、私がよく知っているすべての動的に型付けされたオブジェクトシステムを実装するのに十分です。


の定義を再確認せずに新しい型を追加することはできないため、これはまったく同じではありませんData
ジェド

5
この例では、動的な型付けの概念と弱い型付けを組み合わせています。動的な型指定は、許可された型のリストを定義したり、それらの間で操作をオーバーロードしたりすることではなく、未知の型を操作することです。
hcalves

2
@Jedオブジェクトモデル、基本型、およびプリミティブ操作を実装したら、他の基礎は必要ありません。元の動的言語のプログラムをこの方言に簡単かつ自動的に翻訳できます。
NovaDenizen

2
@hcalvesあなたは私のHaskellコードでオーバーロードに言及しているので、そのセマンティクスについての正しい考えが十分にないのではないかと思います。そこで+、2つのData値を別のData値に結合する新しい演算子を定義しました。 Data動的型システムの標準値を表します。
-NovaDenizen

1
@Jed:ほとんどの動的言語には、「プリミティブ」タイプの小さなセットと、新しい値(リストなどのデータ構造)を導入するためのいくつかの帰納的方法があります。たとえば、Schemeは、原子、ペア、ベクトル以外のほとんどないものです。これらは、指定された動的タイプの残りの部分と同じ方法で実装できるはずです。
ティコンジェルビス

3

膜は、単一のオブジェクトのラッパーではなく、オブジェクトグラフ全体のラッパーです。通常、膜の作成者は、膜内の単一のオブジェクトのみをラップすることから始めます。重要な考え方は、膜を通過するオブジェクト参照自体が同じ膜に推移的にラップされるということです。

ここに画像の説明を入力してください

各タイプは、同じインターフェースを持つタイプでラップされますが、メッセージをインターセプトし、膜を通過するときに値をラップおよびアンラップします。お気に入りの静的型付け言語のラップ関数の型は何ですか?Haskellにはその関数の型があるかもしれませんが、ほとんどの静的に型付けされた言語にはないか、Object→Objectを使用することになり、型チェッカーとしての責任を事実上放棄します。


4
はい、Haskellは実在型を使用してこれを実現できます。型クラスFooがある場合、そのインターフェイスをインスタンス化する任意の型のラッパーを作成できます。 class Foo a where ... data Wrapper = forall a. Foo a => Wrapper a
ジェイクマッカーサー

@JakeMcArthur、説明してくれてありがとう。それが、私が座ってHaskellを学ぶもう1つの理由です。
マイクサミュエル

2
膜は「インターフェース」であり、オブジェクトのタイプは「実在的に型付けされています」-つまり、インターフェースの下に存在することはわかっていますが、それだけです。データ抽象化の実在型は、80年代から知られています。良いrefはあるcs.cmu.edu/~rwh/plbook/book.pdfの章21.1
ドン・スチュワート

@DonStewart。では、Javaプロキシクラスは実在型メカニズムですか?膜が困難になる1つの場所は、その型の定義の外側に見える具体的な型の名前を持つ公称型システムを持つ言語です。たとえばString、Javaの具象型であるため、ラップできません。Smalltalkには入力を試みないため、この問題はありません#doesNotUnderstand
マイクサミュエル

1

誰かが言ったように、理論的には、特定のメカニズムを独自に実装する場合、静的型付けではできなかった動的型付けでできることはほとんどありません。ほとんどの言語は、ボイドポインター、ルートオブジェクト型、または空のインターフェイスなどの型の柔軟性をサポートする型緩和メカニズムを提供します。

より良い質問は、特定の状況や問題で動的型付けがより適切で適切な理由です。

まず、定義します

エンティティ -コード内のエンティティの一般的な概念が必要です。原始数から複雑なデータまで何でもかまいません。

振る舞い -エンティティに何らかの状態と、外部の世界が特定の反応をエンティティに指示できる一連のメソッドがあるとしましょう。このエンティティの状態+インターフェースをその動作と呼びましょう。1つのエンティティは、ツール言語が提供する特定の方法で組み合わされた複数の動作を持つことができます。

エンティティとその動作の定義 -すべての言語は、プログラム内の特定のエンティティの動作(メソッドのセット+内部状態)を定義するのに役立つ抽象化の手段を提供します。これらの動作に名前を割り当てて、この動作を持つすべてのインスタンスが特定のタイプであると言うことができます

これはおそらくそれほど馴染みのないものではないでしょう。あなたが言ったように、あなたは違いを理解しましたが、それでもです。おそらく完全ではなく最も正確な説明ではありませんが、何らかの価値をもたらすのに十分な楽しみを願っています:)

静的型付け -プログラム内のすべてのエンティティの動作は、コードの実行開始前にコンパイル時に検査されます。これは、たとえばPerson型のエンティティにMagicianのような動作をさせる場合(動作させる)、MagicianPersonエンティティを定義し、throwMagic()のようなマジシャンの動作を与える必要があることを意味します。あなたのコードの中で、誤って普通のPerson.throwMagic()コンパイラに伝えると、"Error >>> hell, this Person has no this behavior, dunno throwing magics, no run!".

動的な型付け -動的な型付け環境では、特定のエンティティで何かを実際に試行するまで、エンティティの使用可能な動作はチェックされません。Person.throwMagic()を要求するRubyコードの実行は、コードが実際に来るまでキャッチされません。これはいらいらしますね。しかし、それは啓示にも聞こえます。このプロパティに基づいて、面白いことができます。たとえば、何でもマジシャンに頼ることができるゲームを設計し、コードの特定のポイントに到達するまで誰がそれをするのか本当にわからない、としましょう。そして、カエルが来て、あなたが言うHeyYouConcreteInstanceOfFrog.include Magicその後、このカエルは魔法の力を持つ特定のカエルになります。他のカエル、まだありません。静的型付け言語では、動作の組み合わせの標準的な平均(インターフェイスの実装など)によってこの関係を定義する必要があります。動的型付け言語では、実行時にそれを行うことができ、誰も気にしません。

ほとんどの動的型付け言語には、インターフェイスに渡されるメッセージをキャッチする一般的な動作を提供するメカニズムがあります。たとえば、Ruby method_missingとPHP __callを思い出すといいでしょう。つまり、プログラムの実行時にあらゆる種類の興味深いことを実行し、現在のプログラムの状態に基づいてタイプを決定できるということです。これにより、たとえばJavaのような保守的な静的プログラミング言語よりもはるかに柔軟な問題のモデリング用のツールが提供されます。

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