1 ポイント 投稿者 GN⁺ 4 시간 전 | 1件のコメント | WhatsAppで共有
  • 2000年代から2010年代初頭にかけて静的型の人気はいったん下がったものの、2010年代中盤から後半に再び高まった理由は、静的型システムの品質向上で説明できる
  • 動的型システムは、変数やフィールドの状態・内容を人間が直接判断しなければならず、コンピュータは助けも邪魔もしない 素手で地面を掘ること にたとえられる
  • 初期の Java や C++98 のような過去の静的型システムは、nullable と non-nullable のポインタの区別すら十分に助けられず、型名を繰り返し書かせる 紙のシャベル にたとえられる
  • TypeScript、Haskell、MyPy、Swift、Rust のような現代の型システムは、null 処理、合併型・ユニオン型、型推論を通じて、プログラムのエラー確認と状態表現をよりよく支援する
  • IDE のメソッド名自動補完のような機能が広く普及したことで、静的型システムに入れた情報がエラー検査以外にも 生産性の利点 につながる

中核的な主張

  • 静的型の人気は単なる流行ではなく、広く使える 静的型システム の品質が向上したことで再び高まった、という見方である
  • 良いシャベルがあるなら素手よりシャベルで地面を掘る方がよいが、紙でできたシャベルしかないなら素手の方がましだ、という比喩が使われる
  • 動的型システムでは、プログラムの変数やフィールドがどのような状態や内容を持つかを人間が自分で考えなければならず、コンピュータはその判断を助けない
  • 悪い静的型システムは、助けになるどころか負担の方が大きくなりうるもので、これは紙のシャベルで地面を掘る状況にたとえられる

過去の静的型システムの限界

  • 90年代から2000年代初頭に広く使われた初期の Java や C++98 の静的型システムは、nullable ポインタと non-nullable ポインタを区別するという単純な作業すら十分に助けられなかった
  • 過去の静的型システムは、合併型がなく、積型しかない構造として説明される
  • 過去の静的型システムは、型名をあちこちで手動で書かせた
  • BufferedReader bufferedReader = new BufferedReader(new FileReader(filename)); のようなコードは、小さな災害と表現される

現代の型システムの改善点

  • TypeScript、Haskell、MyPy、Swift、Rust のような現代の型システムは、nullable 型と non-nullable 型を区別する方法を提供する
  • Haskell は Maybe t、TypeScript は T | null、Swift は T?、Rust は Optional<T> を例に挙げ、型システムが null チェックが必要な場所や漏れを知らせられると説明される
  • 実際、ランタイムで null pointer エラーを見ることはほとんどなくなると説明される
  • 現代の型システムは、合併型またはユニオン型の少なくとも一方を提供し、"Make invalid states unrepresentable" の実践を可能にする
  • この方法によって、状態マシンを表すオブジェクトで、各フィールドが関連する状態のときにだけ存在するように表現できる
  • 現代の型システムは 型推論 を提供し、コンパイラが let x = 5; を数値だと判断できるなら、let x: number = 5; と書く必要はない

IDE 機能と結論

  • メソッド名自動補完のような IDE 機能が広く普及したことで、静的型システムの有用性はさらに高まった
  • 90年代には Intellisense は Visual Studio の中核機能だったが、2020年代には同様の機能がほぼすべての IDE とエディタで提供されている
  • 静的型システムに入れた情報は、プログラムのエラー検査だけでなく、追加の 生産性の利点 も生み出す
  • 良い動的型システムは悪い静的型システムより優れているが、今では過去よりはるかに優れた静的型システムを使える

1件のコメント

 
GN⁺ 4 시간 전
Lobste.rs の意見
  • この記事は良いが、完全には同意しない。2000年代初頭の静的型システムは素晴らしいものではなかったとしても、静的型がまったくないよりはずっと良かったと思う
    閉じた合併型はなかったが、サブタイピングでかなりの部分をモデル化できたし、null 不可型はなかったものの、C++ の参照と非ポインタ型、Java のプリミティブ型がその一部を担っていた。Ruby や JavaScript では、あらゆる型が null 許容であるだけでなく、文字列のようにも整数のようにも、プログラム内の他のあらゆる型のようにも扱えてしまうので、よりひどい状況だった
    静的型に対する潮流が変わった大きな理由は、Web 2.0 ソーシャルネットワークのブーム時に先行者利益が何より重要だったからだと思う。Ruby や Python で技術的負債を積み上げるとしても、すばやくリリースして反復するほうが、Friendster や Digg のように押し流されるよりましだったし、遅ければ当時容易に調達できた低金利資金でサーバーを買い足せばよかった
    その後のモバイルブームでは、制御できない制約の多いユーザー端末でソフトウェアが動くようになり、遅い動的型アプリは単純に本当に遅く、型エラーが起きてもサーバーのように最上位のレスポンスハンドラで優雅に復旧することもできなかった。その環境では、静的型の安全性と性能がはるかに説得力を持つようになった

    • Java と 90 年代風の C++ を動的型言語のコードベースと比較して、バグ率が同程度だという論文はかなりあり、動的言語の支持者がこれを静的型は有用ではない証拠としてよく持ち出す
      2000年代初頭には私も同意していた。当時の型システムは、ほとんど間違えようのない性質だけを強制しつつ、コードの構造化には役立たない制約を課しがちだった。特に、サブタイピングと実装継承が結びついたやり方は柔軟ではなかった
      より現代的な型システムを使うようになって考えが変わった。snmalloc では C++ の型システムでメモリ所有権の状態機械を強制し、別のコードベースではリングバッファカウンタの正しいオーバーフロー動作を検証している。どちらも間違うとデバッグが面倒で、よくある誤りの原因だが、実際に正しいと思っていたコードをコンパイル失敗にして、バグがツリーに入るのを防いでくれた
    • 動的型言語で開発するほうが静的型言語より遅いと思う。逆の主張は何度も見るが、理解できない
      IDE で . を押し、メソッド名を少し打って、正しい候補で Enter を押せば、数秒ごとに 2 秒ずつ節約できるし、どんなメソッドがあるかわからないときにクラス定義を探しに行く 30 秒も節約できる。この原理は https://grugbrain.dev/#grug-on-type-systems にもうまく書かれている
      関数パラメータの型を書くより、メソッドを呼び出すコード行を書くほうがはるかに多いのだから、このトレードオフは動的型に圧倒的に不利だ。価値があったのは、実行時に失敗するような馬鹿げたコードを許すことではなく、ローカル変数の型を省略できることだったのであり、静的型言語がそもそもそれを禁止する必要はなかった
    • 2000年代初頭の人気の型システムは、単に「すごくはなかった」という程度ではなく、よくなかったし、非常に冗長だった
      型システムを真面目に使う珍しいコードベースでは、何も語らないコードが何ページ分も積み上がり、それでも実行時条件分岐が山ほどあり、Java なら型階層が増えるほどプログラムも実質的に遅くなった。ほとんどのコードベースは型をまばらに使いながら実行時条件分岐を多用しており、必要なテスト範囲という点で動的型システムに対して大きな節約にはなっていなかった
      動的型言語には静的な見返りはなかったが、簡潔なので読んでレビューしやすく、テストもしやすかった。特に 90 年代末から 2000 年代初頭の依存性注入フレームワークのように、新しいサービスを追加するたびに複数の XML ファイルを直さなければならない環境ではなおさらだった。RAM の半分を食う IDE なしでも作業できた
      私の初期キャリアはまさにこれで、この記事には完全に同意する。Java 1.4 から Java 6 までの費用対効果があまりに悪く、静的型言語をほとんど見限るところだったが、数年後に趣味で Haskell を触ってみて初めて、静的型にも妥当な費用対効果がありうること、問題は Java にあったのだと気づいた。python is not java というエッセイも、あの暗い時代をよく示している
    • 継承ベースのサブタイピングはさらに貧弱だった。網羅性検査のあるパターンマッチングの使い勝手は得られず、実装もあちこちに分散していた
    • 競合より先にサイトを立ち上げてユーザーの前に出し、規模の経済を囲い込むことが重要だったという説明は、今の状況ともかなり重なって聞こえる
  • 静的型が時代精神になって以降、私たちのソフトウェアで信頼性の向上を実際に見たのかは疑問だ。
    静的型の利点は、即時の開発フィードバックと致命的なランタイム障害の減少により強くあると思っていたが、理論上そうした障害は常に起こり得るとしても、実際にはそこまで頻繁に起きていたようには思えなかった

    • 見た。そこそこ大きいTypeScriptのコードベースでTypeScriptエラー0件を目標にし始めてから、undefinednullに対してメソッドを呼ぼうとする試みが急激に減った。
      ジュニアや一部のシニアは最初、あちこちに@ts-ignoreが増えるだろうと懐疑的だったが、実際には依存関係の壊れた型が原因のものまで含めて3件ほどしかなかった。以前は開発ブランチで型の取り違えのせいで週に一度くらいアプリが落ちて作業を妨げられていたが、今では最後にそんなことが起きたのがいつだったかも思い出せない。
      tscを満たすだけでも、自分で直接コードを書いていない場合まで含めて型関連バグが減る。一方で最近のリンターは熱心すぎるし、Sonarのようなツールを満たそうとして実際にリファクタリング破損を見た。警告の95%は誤検知で、3%はツール側のバグ、役に立った2%も実際のバグの原因そのものではなかった。コードベースを合わせるために1週間を費やしてバグを1件直す代わりに、その過程でさらに2件入れてしまった。
      tscを満たす作業では、だいたい1日あたり純粋なバグ修正が2件、リグレッションが1件くらい出たが、リグレッションはたいてい全面クラッシュではなく、誤動作レベルのより低い深刻度だった。
      ここにプロパティベーステストを加えると平均2〜4時間かかり、必ず少なくとも1件のバグが見つかった。コードがプロパティベーステスト可能なら、やるべきだ。
      安価なモデルであるDeepSeek V4 Flashでテスト範囲を広げつつ、ゴミのようなテストが生まれないよう注意すると、1日にロジックバグを2〜3件ほど直せて、クラッシュはなかった。ただしテストスイートは辛うじて保守可能な水準だ。
      ジュニアにSonnetやOpus 4.5、4.6系で雑にテストを作らせたときは、モデルが「現在の挙動を文書化する」だけのテストしか作らず、修正の効果は小さかったうえ、テストスイートは保守不能で廃棄せざるを得なかった。
      モデルベーステストはバグを捕まえるのに非常に優れているが、設定が複雑で、表面的な機能にサイクルを費やさず隅々まで掘り下げるよう誘導するのがとても面倒だ。プロファイルベースのモデルベースファザーのようなものがあれば面白そうだ。
      要するに、型チェッカーは致命的障害やさまざまな混乱をうまく捕まえ、プロパティベーステストは素晴らしい。普通のテストは継続的に見返りを得るには多くの規律が必要だ
    • 個人的にはそうだと思う。自分が使うJavaScriptでは、nullポインタバグはTypeScriptに移行してからほぼ無視できるレベルになったし、同僚も同様だった
  • ここでTypeScriptを優れた型システム一般とひとまとめにすることには、最も同意しづらい

    • その通り。TypeScriptは健全ではなく、特にawaitをまたいだ型の絞り込みには何度も痛い目を見た。それでも状況を劇的に改善したのは事実だ。
      正直、構造的型付けも結局は受け入れるようになったし、今後の言語設計にも良い影響を与えると思う
  • この主張は説得力に欠ける。代数的データ型と型推論を備えたまともなプログラミング言語は、90年代半ばから存在していた。
    JavaやC++の型システムはとても貧弱だったが、SML、OCaml、Haskellはすでにあり、今日と大きく変わらない感触だった。人々がそれらの言語を使わなかったのだとしたら、それは文化、採用、言語化されていない要求の問題であって、「使える型システムが十分によくなかった」だけでは説明できない。
    あるいは「当時の人気言語の型システムは悪く、現代の人気言語の型システムはより良くなったので、型システムがより人気になった」という主張なら、循環論法のように聞こえる。
    型システムと一緒に設計された言語と、もともと型なしで設計され後から型システムが載せられた言語の違いについても、多くのニュアンスがある

  • もともと動的型を好んできた立場から見ても、この文章はかなり公平だと思う。今はC#で仕事をし、趣味ではLispを使い、以前はPythonも使っていた。
    Java 5を使わなければならなかったときは、たいていライブラリ開発者のまずい判断のせいで型システムと延々戦っていた。2010年ごろにC#へ移ってからは、型システムは積極的に有害ではなかったが、概して冗長で、Pythonで最もよくある型の取り違えであるnullポインタ例外も防いではくれなかった。
    C#の型システムが実際に役立ち始めたのは、2020年ごろにnull非許容参照型が入ってからだ。今年はネイティブのユニオン型も入るが、網羅性を強制するユニオン型ライブラリなら少なくとも2016年から可能で、私は2020年から使い始めた。
    流行も依然として役割を果たしていると思うが、その一部は悪くない。より表現力の高い型システムを持つ流行の言語が、私たちが仕事で使う普通の言語にも改善をもたらしてきた

  • Haskellとその型システムは、すでに2000年代にも存在していた。今ほど広く使われてはいなかったが、確かに存在していたのだから、この主張はその点を補うべきだ
    個人的には、TypeScriptが主流言語の利用者により良い型システムをなじみ深いものにした大きな要因だったと思う。品質やMicrosoftの支援に加えて、JavaScriptに適用されるという利点があり、JavaScriptはPythonよりも型が切実に必要だった。"Undefined is not a function." と "The good parts." のせいだ

    • 最新のJavaScript仕様に合わせた “good parts” 本が、簡潔さを保ったまま出てくれるといいのだが
      "Real World Haskell" は2008年に出ていて、主流プログラマーにHaskellをより魅力的に見せようとすることが目標だった。良い知らせを広めるのにどれほど役立ったのかは分からない
      Javaの世界ではScalaが2004年に優れた型を持ち込み、.NETにはF#が2005年に登場した。ScalaはTwitterのような目立つユーザーを最も多く獲得したのかもしれないが、TypeScriptのようにそのプラットフォーム利用者の大きな割合を取り込める位置にはおらず、RustやGoのように他言語の利用者を大量に引きつけるほど魅力的でもなかった
    • 記事はこの問題をすでに扱っている。90年代と2000年代初頭に人気だった初期のJavaやC++98のような 貧弱な静的型システム を紙のシャベルにたとえていた
      その直後の段落でHaskellを「現代的な型システム」として挙げているが、90年代末から2000年代初頭にHaskellの経験がある人は、個人的に少し触った程度まで含めても、実際には0%に近かった。記事が語っているのは、当時の大多数の開発者が静的型言語をどのように経験し、そしてなぜその大多数が静的型言語を集団的に避けたのかということだ
    • HaskellとOCamlは、ある程度 ツールのエコシステム が弱いために苦労していると思う。言語自体は素晴らしいが、ツール面での数多くの小さな紙の切り傷のせいで採用を失っている
      例えばOCamlで dune を使うには、opam ファイル、dune ファイル、ocaml module 構文、ocaml 構文を理解しなければならない。Haskellの選択的なコンパイラ拡張も同じように恐ろしく感じられる
      cargo では toml とRustだけ知っていればよいのとは対照的だ