静的型付けは、より大きなプロジェクトでどのように役立ちますか?


9

スクリプトプログラミング言語のサイトのメインページで好奇心をかきながら、次の文章に出会いました。

システムが大きくなりすぎて頭に残ることができない場合は、静的型を追加できます。

これにより、静的なコンパイル言語(Javaなど)と動的なインタープリタード言語(主に使用頻度が高いためPythonが主流ですが、ほとんどのスクリプト言語間で共有される「問題」)の間の多くの宗教戦争で、静的な不満の1つでした。動的に型付けされた言語に対する型付き言語のファンは、「いつか、関数の戻り値の型を忘れ、それを調べなければならず、静的に型付けされた言語ではすべてであるため、より大きなプロジェクトにうまくスケーリングできない」明示的に宣言されています。」

このような発言は理解できませんでした。正直に言うと、関数の戻り値の型を宣言したとしても、コードの多くの行を記述した後はそれを忘れることができ、その関数の検索関数を使用して宣言されている行に戻る必要があります。それをチェックするあなたのテキストエディタ。

さらにとして、関数がで宣言されているとしてtype funcname()...、知っwhitout typeあなただけ知っているので、あなたは関数が呼び出された各ラインを超える検索する必要がありますfuncnameしばらくPythonで、あなたのように、coudだけを検索def funcnameまたはfunction funcnameだけで、一度たまたま宣言。

さらに、REPLでは、関数の戻り値の型をさまざまな入力でテストするのは簡単ですが、静的に型付けされた言語では、コード行を追加し、宣言された型を知るためにすべてを再コンパイルする必要があります。

では、静的型付け言語の強みではない関数の戻り値の型を知る以外に、静的型付けは大規模なプロジェクトでどのように役立つのでしょうか。



2
他の質問への回答を読むと、おそらくこの質問に必要な回答が得られます。彼らは基本的に、異なる観点から同じことを尋ねています:)
sara

1
SwiftとPlaygroundは、静的に型付けされた言語のREPLです。
daven11

2
言語はコンパイルされていません。実装はコンパイルされています。「コンパイルされた」言語用のREPLを書く方法は、言語を解釈できるものを書くか、少なくとも必要な状態を維持しながら、行ごとにコンパイルして実行することです。また、Java 9にはREPLが付属しています。
Sebastian Redl

2
@ user6245072:インタープリターのREPLを作成する方法は次のとおりです。コードを読み取り、それをインタープリターに送信し、結果を出力します。コンパイラーのREPLを作成する方法は次のとおりです。コードを読み取り、それをコンパイラーに送信し、コンパイルされたコードを実行し、結果を出力します。やさしい。それこそが、FSi(F♯REPL)、GHCi(GHC HaskellのREPL)、Scala REPL、およびClingが行うことです。
イェルクWミッターク

回答:


21

さらに、REPLを使用すると、関数の戻り値の型をさまざまな入力でテストすることは簡単です

ささいなことではありません。それはまったく簡単はありません。簡単な関数に対してこれを行うのは簡単です。

たとえば、戻り値の型が完全に入力の型に依存する関数を簡単に定義できます。

getAnswer(v) {
 return v.answer
}

この場合、getAnswer実際は単一の戻り値の型はありません。戻り値の型が何であるかを知るために、サンプル入力でこれを呼び出すテストを書くことはできません。それは常に実際の引数に依存します。実行時。

そして、これには、たとえばデータベースの検索を実行する関数も含まれていません。または、ユーザー入力に基づいて物事を行います。または、もちろん動的型であるグローバル変数を検索します。または、ランダムなケースで戻り値の型を変更します。言うまでもなく、すべての個々の機能を毎回手動でテストする必要があります。

getAnswer(x, y) {
   if (x + y.answer == 13)
       return 1;
   return "1";
}

基本的に、一般的なケースで関数の戻り値の型を証明することは、文字通り数学的に不可能です(停止問題)。唯一の方法保証戻り値の型は、この質問に答えることは証明可能でないプログラムを許可しないことにより、停止問題のドメインの下に落ちないよう入力を制限することであり、これは静的型付けが何をするかです。

さらに、関数はタイプfuncname()...で宣言されているため、タイプがわかっている場合、関数が呼び出される各行を検索する必要があります。これは、Pythonなどでは、funcnameしか知らないためです。宣言時に一度だけ発生するdef funcnameまたはfunction funcnameを検索します。

静的型付け言語には「ツール」と呼ばれるものがあります。これらは、ソースコードを使用して作業を行うのに役立つプログラムです。この場合、Resharperのおかげで、右クリックして定義に移動するだけです。または、キーボードショートカットを使用します。または、マウスを上に置くだけで、関係するタイプがわかります。私はgreppingファイルについて少しでも気にしません。テキストエディター自体は、プログラムのソースコードを編集するための哀れなツールです。

メモリからdef funcnameは、関数を任意に再割り当てできるため、Pythonでは十分ではありません。または、複数のモジュールで繰り返し宣言することもできます。またはクラスで。等。

それを確認するには、テキストエディタの検索機能を使用して宣言されている行に戻る必要があります。

関数名をファイルで検索することは、決して必要とされるべきではない恐ろしいプリミティブな操作です。これは、環境とツールの根本的な障害を表しています。Pythonでテキスト検索が必要になることさえ考えられるという事実は、Pythonに対する大きな問題です。


2
公平を期すために、これらの「ツール」は動的言語で発明されたものであり、動的言語は静的言語よりもずっと前にそれらを備えていました。定義に移動、コード補完、自動リファクタリングなどは、静的言語にグラフィックやIDEはもちろんのこと、グラフィックIDEさえ含まれる前に、グラフィックLispおよびSmalltalk IDEに存在していました。
イェルクWミッターク

関数の戻り値の型を知ることは、常に関数が何を教えてくれないDO。タイプを記述する代わりに、サンプル値を使用してドキュメントテストを記述することもできます。たとえば、(words 'some words oue')=> ['some'、 'words'、 'oeu']と(words string)-> [string]、(zip {abc} [1..3])を比較します=> [(a、1)、(b、2)、(c、3)]とそのタイプ。
aoeu256

18

長年にわたって変化した、多くのプログラマーがいるプロジェクトを考えてみてください。これを維持する必要があります。機能あり

getAnswer(v) {
 return v.answer
}

それはいったい何をするのでしょうか?なにv?要素はanswerどこから来たのですか?

getAnswer(v : AnswerBot) {
  return v.answer
}

もう少し情報があります—; タイプが必要ですAnswerBot

クラスベースの言語に行くと、

class AnswerBot {
  var answer : String
  func getAnswer() -> String {
    return answer
  }
}

これで、型の変数をAnswerBot取得してメソッドgetAnswerを呼び出すことができ、誰もがそれが何をするかを知っています。ランタイムテストが行​​われる前に、変更はコンパイラによってキャッチされます。他にも多くの例がありますが、おそらくこれはあなたにアイデアを与えますか?


1
すでに明確に見えます-そのような関数が存在する理由がないことを指摘しない限り、もちろんそれは単なる例です。
user6245072

大規模なプロジェクトに複数のプログラマーがいて、そのような関数が存在する場合(さらに悪い場合)は、それが厄介な問題です。また、動的言語の関数がグローバル名前空間にあることを考慮してください。そうすれば、時間の経過とともにいくつかのgetAnswer関数が存在する可能性があります。これらの関数は両方とも機能し、異なるときに読み込まれるため、両方とも異なります。
daven11

1
それは関数型プログラミングの誤解が原因だと思います。しかし、それらがグローバル名前空間にあるとはどういう意味ですか?
user6245072

3
「動的言語の関数はデフォルトでグローバル名前空間にあります」これは言語固有の詳細であり、動的型付けによる制約ではありません。
サラ2016

2
@ daven11「ここでjavascriptを考えています」というのは確かですが、他の動的言語には実際の名前空間/モジュール/パッケージがあり、再定義について警告することができます。少し一般化しすぎているかもしれません。
コアダンプ2016

10

判断を曇らせる可能性のある大規模な静的プロジェクトでの作業について、いくつかの誤解があるようです。ここにいくつかのポインタがあります:

関数の戻り値の型を宣言したとしても、コードの多くの行を記述した後はそれを忘れることができます。また、テキストエディターの検索関数を使用して宣言されている行に戻る必要があります。確認してください。

静的に型付けされた言語を扱うほとんどの人は、言語用のIDEまたは言語固有のツールと統合されたインテリジェントエディター(vimやemacsなど)を使用します。通常、これらのツールで関数のタイプをすばやく見つける方法があります。たとえば、JavaプロジェクトのEclipseでは、通常、メソッドのタイプを見つける方法が2つあります。

  • 「this」以外のオブジェクトでメソッドを使用する場合は、参照とドット(例:)を入力すると、someVariable.Eclipseがのタイプを検索し、そのタイプでsomeVariable定義されているすべてのメソッドのドロップダウンリストを表示します。リストを下にスクロールすると、選択するとそれぞれのタイプとドキュメントが表示されます。これは、動的言語で達成するのが非常に難しいことに注意してください。これは、エディターがタイプを判別するのが難しく(場合によっては不可能)someVariable、正しいリストを簡単に生成できないためです。メソッドを使用したい場合は、thisctrl + spaceを押すだけで同じリストを取得できます(ただし、この場合、動的言語ではそれほど難しくありません)。
  • 特定のメソッドへの参照が既に記述されている場合は、その上にマウスカーソルを移動すると、メソッドのタイプとドキュメントがツールチップに表示されます。

ご覧のとおり、これは動的言語で使用できる一般的なツールよりもいくらか優れています(動的言語ではこれは不可能ではありません。IDEの機能がかなり優れているため、smalltalkはすぐに思いつくものですが、動的言語であるため、利用可能性が低くなります)。

さらに、関数はタイプfuncname()...で宣言されているため、タイプがわかっている場合、関数が呼び出される各行を検索する必要があります。これは、Pythonなどでは、funcnameしか知らないためです。宣言時に一度だけ発生するdef funcnameまたはfunction funcnameを検索します。

静的言語ツールは通常、セマンティック検索機能を提供します。つまり、テキスト検索を実行する必要なく、特定のシンボルの定義と参照を正確に見つけることができます。たとえば、JavaプロジェクトにEclipseを使用すると、テキストエディターでシンボルを強調表示して右クリックし、「定義に移動」または「参照を​​検索」を選択して、これらの操作のいずれかを実行できます。関数定義のテキストを検索する必要はありません。エディターがすでにそれを正確に知っているからです。

ただし、逆に、テキストによるメソッド定義の検索は、大規模な動的プロジェクトでは実際にはうまく機能しないことを示しています。そのようなプロジェクトには同じ名前のメソッドが複数存在する可能性があり、おそらくどのツールを起動するかを明確にするためのすぐに利用できるツール(そのようなツールはせいぜい書くのが難しい、または一般的なケースでは不可能であるため)なので、手動で行う必要があります。

さらに、REPLを使用すると、関数の戻り値の型をさまざまな入力でテストすることは簡単です

静的に型付けされた言語のREPLを持つことは不可能ではありません。Haskellが思い浮かぶ例ですが、他の静的型付け言語のREPLもあります。ただし、重要な点は、静的言語で関数の戻り値の型を見つけるためにコードを実行する必要がないことです。これは、何も実行せずに調べることで判別できます。

静的に型付けされた言語では、宣言された型を知るためだけに、コード行を追加してすべてを再コンパイルする必要があります。

これを行う必要がある場合でも、すべてを再コンパイルする必要はないでしょう。最近のほとんどの静的言語には、変更されたコードのごく一部のみをコンパイルするインクリメンタルコンパイラーがあり、型エラーを作成した場合、ほぼ瞬時にフィードバックを得ることができます。たとえば、Eclipse / Javaは、入力中にタイプエラー強調表示します


4
You seem to have a few misconceptions about working with large static projects that may be clouding your judgement.ええと、私はまだ14歳で、Androidでは1年足らずからしかプログラムを作成していないので、それは可能だと思います。
user6245072

1
IDEがなくても、Javaのクラスからメソッドを削除し、そのメソッドに依存することがある場合、Javaコンパイラーはそのメソッドを使用していたすべての行のリストを提供します。Pythonでは、実行中のコードが不足しているメソッドを呼び出すと失敗します。私はJavaとPythonの両方を定期的に使用していますが、実行速度を上げ、JavaがサポートしていないクールなことができるPythonが大好きですが、実際には、Pythonプログラムで問題が発生し、 (ストレート)Java。特にリファクタリングはPythonでははるかに困難です。
JimmyJames

6
  1. 静的チェッカーは静的型付け言語の方が簡単だからです。
    • 少なくとも、動的言語機能がなく、コンパイルされた場合、実行時に未解決の関数はありません。これは、ADAプロジェクトおよびマイクロコントローラーのCでは一般的です。(マイクロコントローラプログラムは時々大きくなります...何百ものklocのように大きくなります。)
  2. 静的コンパイル参照チェックは、関数の不変条件のサブセットであり、静的言語ではコンパイル時にチェックすることもできます。
  3. 静的言語は通常、参照の透過性が高くなります。その結果、新しい開発者は1つのファイルに飛び込んで何が起こっているのかを理解し、コードベースのすべての奇妙なことを知らなくても、バグを修正したり、小さな機能を追加したりできます。

たとえば、JavaScript、Ruby、Smalltalkと比較してください。開発者は実行時にコア言語機能を再定義します。これにより、大きなプロジェクトの理解が難しくなります。

より大きなプロジェクトには、より多くの人がいるだけでなく、より多くの時間があります。みんなが忘れたり、先に進むのに十分な時間。

事例として、私の知人はLispで安全な「Job For Life」プログラミングを行っています。チーム以外の誰もコードベースを理解できません。


Anecdotally, an acquaintance of mine has a secure "Job For Life" programming in Lisp. Nobody except the team can understand the code-base.本当にそんなに悪いのでしょうか?彼らが追加したパーソナライゼーションは、生産性の向上に役立ちませんか?
user6245072

@ user6245072これは現在そこで働いている人々にとっては有利かもしれませんが、新しい人々を採用することを難しくします。非主流の言語をすでに知っている人を見つけるか、まだ知られていない言語を教えるには、もっと時間がかかります。これは、プロジェクトが成功したときにスケールアップしたり、変動から回復したりするのを難しくする可能性があります-人々は離れて移動し、他のポジションに昇進します...しばらくすると、専門家自身にとっても不利になる可能性があります- 10年ほどニッチ言語を書いてしまったら、何か新しいものに移るのは難しいかもしれません。
ハルク、

トレーサーを使用して、実行中のLispプログラムから単体テストを作成できませんか?Pythonのように、関数を受け取り、引数を出力する変更された関数を返す、print_argsと呼ばれるデコレータ(adverb)を作成できます。これをsys.modulesのプログラム全体に適用できますが、sys.set_traceを使用する方が簡単です。
aoeu256

@ aoeu256 Lispランタイム環境の機能に慣れていません。しかし、マクロを多用していたため、通常のlispプログラマはコードを読み取ることができませんでした。マクロがLispに関するすべてを変更するため、ランタイムに対して「単純な」ことを行おうとしても機能しない可能性があります。
Tim Williscroft

@TimWilliscroftそのようなことをする前に、すべてのマクロが展開されるまで待つことができます。Emacsには、マクロをインライン展開するためのショートカットキーがたくさんあります(おそらくインライン関数も)。
aoeu256

4

このような発言は理解できませんでした。正直に言うと、関数の戻り値の型を宣言したとしても、コードの多くの行を記述した後はそれを忘れることができ、その関数の検索関数を使用して宣言されている行に戻る必要があります。それをチェックするあなたのテキストエディタ。

それはあなたが戻り値の型を忘れることについてではありません-これは常に起こりそうです。これは、戻り値の型を忘れたことをツールが通知できるようにするためのものです。

さらに、関数はtype funcname()...で宣言されているので、タイプがわかっている場合は、関数が呼び出された各行を検索する必要があります。なぜならfuncname、Pythonなどでは、ただ検索するdef funcnamefunction funcname、一度だけしか実行できないためです。、宣言時。

これは構文の問題であり、静的型付けとはまったく無関係です。

特別なツールを自由に使用せずに宣言を検索する場合、Cファミリーの構文は実際には使いにくいものです。他の言語にはこの問題はありません。Rustの宣言構文を参照してください。

fn funcname(a: i32) -> i32

さらに、REPLでは、関数の戻り値の型をさまざまな入力でテストするのは簡単ですが、静的に型付けされた言語では、コード行を追加し、宣言された型を知るためにすべてを再コンパイルする必要があります。

どの言語も解釈でき、どの言語もREPLを持つことができます。


では、静的型付け言語の長所ではない関数の戻り値の型を知る以外に、静的型付けは大規模なプロジェクトでどのように役立つのでしょうか。

抽象的に答えます。

プログラムはさまざまな操作で構成され、これらの操作は、開発者が行ういくつかの前提のために、それらがそうであるようにレイアウトされています。

一部の前提は暗黙的であり、一部は明示的です。いくつかの仮定はそれらの近くの操作に関係し、いくつかはそれらから離れた操作に関係しています。仮定は、それが明示的に表現され、真理値が重要である場所に可能な限り近くなると、特定が容易になります。

バグとは、プログラムに存在するが一部のケースには当てはまらない仮定の現れです。バグを追跡するには、誤った仮定を特定する必要があります。バグを削除するには、その仮定をプログラムから削除するか、仮定が実際に満たされるように何かを変更する必要があります。

仮定を2種類に分類したいと思います。

最初の種類は、プログラムの入力に応じて、保持される場合とされない場合がある前提です。この種の誤った仮定を特定するには、プログラムのすべての可能な入力のスペースを検索する必要があります。知識に基づいた推測と合理的な思考を使用して、問題を絞り込み、はるかに小さなスペースで検索できます。しかし、それでも、プログラムが少しでも大きくなると、その初期入力スペースは非常に大きな速度で増加し、あらゆる実用的な目的で無限と見なすことができるようになります。

2番目の種類は、すべての入力に確実に当てはまる、またはすべての入力に間違いなく誤った仮定です。この種の仮定を誤りであると識別した場合、プログラムを実行したり、入力をテストしたりする必要さえありません。この種の仮定が正しいと判断すると、バグ(すべてのバグ)を追跡しているときに気になる疑いが1つ少なくなります。したがって、できるだけ多くの仮定がこの種類に属することに価値があります。

2番目のカテゴリ(入力に関係なく常にtrueまたは常にfalse)に仮定を置くには、仮定が行われる場所で利用できる最小限の情報が必要です。プログラムのソースコード全体で、情報はすぐに古くなります(たとえば、多くのコンパイラーは手続き間分析を行わないため、ほとんどの情報で呼び出しがハード境界になります)。必要な情報を最新に保つ方法が必要です(有効で近くにあります)。

1つの方法は、この情報のソースを、それが消費される場所にできるだけ近づけることですが、ほとんどの場合、それは非現実的です。もう1つの方法は、情報を頻繁に繰り返し、ソースコード全体で関連性を更新することです。

すでにおわかりのように、静的型はまさにそれです-タイプ情報のビーコンがソースコード全体に散在しています。その情報を使用して、型の正しさに関するほとんどの仮定を2番目のカテゴリーに入れることができます。つまり、ほとんどすべての操作は、型の互換性に関して常に正しいか常に正しくないかとして分類できます。

私たちのタイプが正しくない場合、分析により、バグが遅くなるのではなく早期に注目されるようになり、時間を節約できます。タイプが正しい場合、分析により、バグが発生したときにすぐにタイプエラーを除外できるため、時間を節約できます。


3

古いことわざ「ガベージイン、ガベージアウト」を覚えていますか。まあ、これは静的型付けが防ぐのに役立つものです。これは万能の万能薬ではありませんが、ルーチンがどのような種類のデータを受け入れて返すかについての厳格さは、正しく処理していることを保証することを意味します。

したがって、整数を返すgetAnswerルーチンは、文字列ベースの呼び出しで使用しようとすると役に立ちません。静的型付けは、おそらく間違いを犯していることに気をつけるようにすでに指示しています。(そして、確かに、それをオーバーライドできますが、それがあなたがやっていることを正確に知っている必要があり、キャストを使用してコードでそれを指定する必要があります。四角い穴への丸い止め釘は結局うまくいきません)

これで、複雑な型を使用したり、鉱石の機能を持つクラスを作成したりすることで、さらに処理を進めることができます。これらを渡し始めると、プログラム内の構造が突然大きくなります。構造化プログラムとは、正しく機能させ、維持するのがはるかに簡単なプログラムです。


静的型推論(pylint)を行う必要はありません。動的型推論を行うことができますchrislaffra.blogspot.com/2016/12/…これもPyPyのJITコンパイラによって行われます。動的型推論の別のバージョンもあり、コンピューターは引数にモックオブジェクトをランダムに配置し、エラーの原因を確認します。99%の場合、停止の問題は問題になりません。時間がかかりすぎる場合は、アルゴリズムを停止するだけです(これは、Pythonが無限再帰を処理する方法であり、再帰制限を設定できます)。
aoeu256
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.