1 ポイント 投稿者 GN⁺ 2023-10-05 | 1件のコメント | WhatsAppで共有
  • 20年以上ソフトウェアを書いてきた経験から、強力な静的型付けは、REPLやワンオフのスクリプトのような例外を除けば、ほとんど常に選ぶ価値がある
  • 型は呼び出し側と呼び出される側の間の契約をコードに残し、不正な引数や戻り値をコンパイル・型チェック時点で弾ける
  • HTML入力から来た文字列 "20" が数値のように使われて "201" になる例は、実行前に捕まるエラーと顧客に露出するエラーの違いを示している
  • SvixはRedisキー、キャッシュ値、PersonIdPetId のような識別子、API入力検証を型システムに載せて、タイプミスや誤ったIDの受け渡しを減らそうとしている
  • 型を省略すると初期実装は速くなるかもしれないが、ドキュメント化・テスト・デバッグのコストが大きくなり、型推論とIDE支援を使えばリファクタリングやオンボーディングが容易になる

静的型にこだわる理由

  • 強力な静的型付けは良いアイデアというだけでなく、ほとんどのソフトウェアにおける正しいデフォルトに近い
  • 型のない言語やその変種にも有用な場面はある
    • REPLの利用
    • もともとほとんど型のない環境でのワンオフスクリプト。たとえばシェルなど
  • それ以外のほとんどの場合では強い型を好む
  • 型を使わないと当面の開発速度は上がるかもしれないが、それは「崖に向かって全速力で進むこと」に近いと考えている
  • 選択肢は結局のところ2つのどちらか
    • より多くの労力を払って、不変条件をコンパイルまたは型チェック時点で確認する
    • 労力を減らして実行時に確認するか、あるいは実行時ですら確認しない
  • 実行時エラーは開発中に必ず捕まるわけではなく、捕まったとしても顧客に見える形で発生することがある
  • テストは役に立つが、あり得るすべての不正な関数引数型をテストするのは難しく、型で不正な型を防ぐほうが容易だと考えている

型はコード契約とバグ削減に直結する

  • 型は人間とツールの両方にとって有用なコード注釈であり、コード片同士の契約をより厳密にする仕組みでもある
  • 同じ誕生日お祝い関数でも、契約の明確さは大きく異なる
    • birthdayGreeting1(...params) は引数の数すら見えず、ドキュメントを読まなければ動作を把握しにくい
    • birthdayGreeting2(name, age) は名前と年齢があることは分かるが、型はない
    • birthdayGreeting3(name: string, age: number): string は入力と戻り値の型まで契約に含めている
  • 関数が age + 1 を使うように変わると、型のない版では文字列入力で問題が起きる
    • HTML入力から来た値は常に文字列であり得る
    • birthdayGreeting2("John", "20")"John will turn 201 next year!" を返す
    • 型のある版では age が数値でなければならないため、不正な呼び出しはコンパイルに失敗する
  • 呼び出し側と呼び出される側の間の契約は、コードベースが大きくなるほど重要になる
    • 呼び出される側が変わったときに、呼び出し側が受ける影響を把握できる
    • オープンソースライブラリのように、異なる人が呼び出し側と呼び出される側を書く場合は特に重要
  • こうした契約がないと、変更の影響がどこまで及ぶかを把握しにくい

開発体験、リファクタリング、オンボーディングでの利点

  • 型情報はIDEや開発ツールに活用され、開発体験を大きく改善する
  • コードを書いている最中に期待が外れていることをすぐ把握できるため、認知負荷が減る
  • 開発者は現在の文脈にあるすべての変数や関数の型を覚えておく必要がなく、コンパイラが食い違いを知らせてくれる
  • リファクタリングもしやすくなる
    • 関数実装を変えたときに、ほかの場所の前提を壊していないかをコンパイラが教えてくれる
  • 新しいエンジニアがコードベースやライブラリに慣れるのも容易になる
    • 型定義をたどりながら、どこで使われているかを理解できる
    • 変更するとコンパイルエラーが起きるので、試行しやすい
  • Person 型を受け取る関数の例で違いがはっきりする
    • birthdayGreeting3(person: Person)Person の使用箇所をIDEで見つけやすい
    • 型のない birthdayGreeting2(person) が実際には Person を期待していることは、コードベース全体を読まないと分からない
  • ドキュメントで一部を補うことはできるが、ドキュメントは古くなりやすく、型はコードそのものに残るドキュメントになる
  • 型は、有用な変数名をさらに強力にしたものに近いと考えている

Svixが型システムに情報を載せる方法

  • Svixは可能な限り多くの情報を型システムに載せて、コンパイル時点で捕まえられるエラーを減らし、開発体験も改善しようとしている
  • Redisは本質的に文字列ベースのプロトコルで組み込み型がないため、Redis層では型の利点が失われかねない
  • 単純なキャッシュ例には2つのバグがある
    • person-{id}preson-{id} のようなキー名のタイプミス
    • 人物データを Pet 型として読み込もうとしている
  • Svixはこうした問題を避けるために2つの方法を取っている
    • キーに通常の文字列ではなく特定の型を要求する
    • キーと値を強制的に対応付ける
  • たとえば PersonCacheKey::new(id) で作ったキーを使うと、cache.get(PersonCacheKey::new(id)) の結果を Pet として受け取ろうとするコードはコンパイルに失敗する
  • 単純な String ID もミスを招きやすい
    • do_something(id: String) では、どのIDを受け取るべきかが明確でない
    • pet.id を渡すべき場所で、実際には pet.owner を渡すべきなのに取り違えるミスが起こり得る
  • Svixは各IDに別個の型を持たせている
    • PersonId(String)
    • PetId(String)
    • PetownerPersonId
  • APIで受け取ったIDの妥当性も型生成と結び付けている
    • たとえばpet IDは pet_ 接頭辞の後ろにKsuidが付く形式
    • PetId は検証なしでは生成できないようにしている
    • この方式なら、データベースでpetが見つからず 404 Not Found を返すとき、IDの形式自体は有効だったと確信できる
    • 無効なIDはAPIハンドラの時点で 422 または 400 として処理される

反対論点とツールの役割

  • 型に対する主な反対論点は、開発速度、学習曲線と型の複雑さ、労力とボイラープレートである
  • 型なしでプロトタイピングすれば確かに速いことはある
    • コンパイラに文句を言われずにコードをコメントアウトできる
    • 正しい値が決まるまでフィールドに不正な値を入れておける
  • しかし、これは攻撃的で不要な技術的負債であり、ローカル・テストスイート・本番環境でのデバッグ時に何度もコストを払うことになると考えている
  • 学習曲線は存在するが、ほとんどの人は型の専門家になる必要はない
    • 単純な型表現だけでも十分に作業できる
    • 行き詰まった場合に助けを求めればよい
  • 開発者はすでにコーディングやReact、Axumのようなフレームワークなど多くを学ばなければならず、型学習の負担は誇張されていると考えている
  • 型学習は一度きりのコストであり、特定のコードベースにオンボーディングするときに型の助けで得られる利点のほうが大きい
  • 型を使わないと基本的な安全性を得るために相当量のドキュメント化とテストが必要になる
    • ドキュメントとテストは古くなり得る
    • 正しい型を追加するほうが労力は少ないと考えている
  • 型推論のない言語では型記述が煩雑になることがある
    • Javaの例では Person person1 = newPerson(); のような繰り返しが生じる
    • 記事にはその後、Javaにも型推論があるという訂正が追記されている
  • Rustのように型推論のある言語では let person1 = new_person(); のようにより簡潔になる
  • 型の利点を得るには、言語を理解する最新のコード補完機能を備えたコードエディタまたはIDEが必要になる
  • vimemacs、タブ対スペースのような好みの論争と違って、型はコストに対する利点が大きく、使わない理由を理解しがたいという立場である
  • 続編として using the type system effectively がある

1件のコメント

 
GN⁺ 2023-10-05
Hacker News のコメント
  • この議論でいちばんもどかしいのは、すべてが人々がどう感じているかについての話で、実証的な根拠が乏しい点です。
    既存の研究では、2つの方式の間に意味のある差はないと見られており、新しい研究がない限り、それぞれが好む側が確実に正しいと断定するのは難しいでしょう。
    個人的には型のある言語が好きですが、TypeScript のような型システムは不十分です。ランタイムで型を実際に使えないためランタイムバグが残り、多くのランタイムロジックを型システムにエンコードできないので、不可能なケースを依然として自分で検査することになります。
    型システムがランタイムバグを考える必要をほとんどなくしてくれるなら圧倒的な利点になりますが、ほとんどの言語はその水準にはなく、オーバーヘッドと一部の利点のあいだの曖昧な中間地点にとどまっています。
    バグ数や速度で大きな差が出ない理由は、結局は相殺されるからだと思います。型のセーフティネットがなければテストをより多く書くようになり、逆に型システムを過信すれば結局、同じくらいの量のランタイムバグが残ります。このテーマについてしっかりした研究があればよいのですが、難しい問題です。

    • ほとんどの人は、型が多くのバグを防ぐことには同意すると思いますし、このスレッドにもそうした研究が投稿されています。
      核心はそれよりも、型に投資する価値はないと判断する主観的な理由が何か、という点に近いです。
    • 結局は判断の問題として受け入れるしかないように思います。
      数年前に開発者の生産性に関する研究を調べましたが、ほとんどはひどいものか、ジュニアにしかまともに適用できない内容でした。たとえば初心者は、静的エラーに対する素早いフィードバックから大きな利益を得ます。
      良い実験設計を大学生ではなく専門家に適用するのはほぼ不可能で、個人差・開発の種類・管理手法など無数の変数を切り分ける必要があるため、シグナルを取り出すのは困難です。悲しいことですが、人生の多くのことは効果的に測定しにくいものです。
    • 私の考えもほぼ同じです。
      記事や多くのコメントは、プログラマーの利便性、生産性、「正確性」について語っていますが、現在の研究では静的型がこれらを改善または悪化させるという意味のある結果はありません。事実上、主観的なものです。
      ただし、静的型が実際にもたらす効果のひとつは、ささやかながら証明可能です。より効率的なコードを書けるようにするという点です。型規律の議論ではこれが中心であるべきで、それ以外は現段階では雲をつかむような話に近いです。
      記事で取り上げられている TypeScript は実際には強い型付けではなく、静的型付けではありますが弱い型付けです。型は注釈に近く、性能やメモリ配置の保証もありません。そのため、ドキュメント化以外では、静的型のコストを払っているにもかかわらず実質的な利点はほとんど得られません。
      技術コミュニティが実際の根拠を無視し、文化的・個人的な好みを事実のように受け入れているのは驚きです。
    • 静的型は、より信頼できるソフトウェアのためのスイスチーズの一枚にすぎません。
      他の手法と同じく穴があるので、最大限の信頼性を得るには複数の手法を組み合わせる必要があります。すべてを捕まえられないからといって静的型を捨てるのは、泥棒が窓を割れるからドアに鍵をかけないと言うのに似ています。セキュリティが本当に重要なら、ドアにも鍵をかけ、窓にも鉄格子を付けるべきで、どちらか一方だけを選ぶものではありません。
    • 型を表現する言語でプログラムを書けば「ランタイムバグ」が魔法のように消えると考えているように見えます。
      言語が一般的なプログラムを書けるほど強力なら、バグも作れるほど強力です。
      静的型は特定の種類のバグを捕まえるのに有効な場合がありますが、すべてではありません。ときには静的なユニットテストや、実行可能なドキュメント用のドメイン特化言語のように、可読性を高めてくれます。
      一般に動的言語はより機敏で、テストをより簡単に多く書けます。静的型付き言語であれば書かなくてよいテストもあるので型は依然として有用ですが、よく信じられているほど普遍的に強力ではありません。
  • 静的型付けを好むべきだという社会的圧力とは別に、結局静的型から離れた理由は、いつもその周囲に象牙の塔が築かれていたからだ
    どちらのパラダイムでもそれぞれ10年ずつソフトウェアを作ってきたが、今は型システムを使わないほうを好む
    動的型付けは、単体テストが組み合わせ可能なコードを強制するように、単純なコードを書くよう促す強制力のように感じる。読みやすく理解しやすいコードのことだ
    初心者開発者がコードベースに入りやすくなるという主張にもあまり賛成できない。理解しないまま赤い表示を消すだけの反復ループを助長しやすいからだ。型システムはプロジェクトごとに、言語の上にもう一つ、非常にドメイン特化した言語を載せて学ばせることになり、実際の動作理解を妨げることが多い
    本文で挙げられている問題は、型と同じくらい堅牢でありながら理解しやすい方法で解ける。型を単純に使うことはできるだろうが、私の経験では実際にはほとんどそうではなかった。補完も好きではないので、そう受け取ってほしい
    「コードこそがドキュメントだ」と叫ぶ年老いた開発者にすぎないのかもしれないが、最近の業界にあふれる「ChatGPTが正しいと言ったし、給料は高い」開発者たちへの深い不満から出た考えなのかもしれない

    • 動的型付けが大半の開発者に実際に単純なコードを書かせるなら、非常に強い主張になるだろう
      しかし一般的には、反対の証拠のほうが強く見える。実際の動的コードを文書化しようとして後から作られた型は、最初から静的型で実装された同じ機能よりはるかに複雑な場合が多い。TypeScriptエコシステムのDefinitelyTypedが数多くの事例を提供している
      それらの型が「単純に使われている」とは見なしにくいが、その複雑さは型システム自体や型定義の提供方法から来ているのではなく、それが説明している動的コードの複雑さから来ている
      最初から静的型で作られた同等のパッケージは、たいていインターフェイスがより単純だ。型を後から既存APIに合わせ込むのではなく、先に定義するからだ
      インターフェイスを明示しなければ、そのインターフェイスが単純なのか複雑なのかすら分からないと思っている。「コードがドキュメント」という理想には同意するが、インターフェイスを明示するコードがなければ、そのインターフェイスは定義上、文書化が不十分だ
    • 「型システムが初心者開発者の理解を妨げ、赤い表示を消すだけにさせる」という話は、最初に聞くと正反対に感じる
      実際にそういう場面を横で見てみたいくらいだ。私の分野では、ドメイン特化のロジックは動的型のコードベースではほとんど理解不能なのに対し、静的型のコードは開発者にビジネスロジックを教えてくれる
      「コードがドキュメント」という言葉も、むしろ混乱する。私の経験では、静的型があってこそコードはドキュメントになる。なければオブジェクトにどんな属性があるのか、存在しないと思っていた属性をなぜチェックしているのかを知る方法がない。コメントはあるが、意味のあるコメントを残す人はほとんど見たことがない
    • こうした論理はほぼ全部が逆に感じられて、理解しにくい
      私の経験は逆だ。非常に動的なパターンは適切に型を付けるのが難しく、良い型システムはより単純なパターンを促すので、型も単純になる
      赤い表示を消すことは重要だ。赤い表示は問題があるという意味であり、他の方法でエラーを見つけるよりずっと簡単だ。なぜそのエラーを後で見つけたいのか分からない
      補完も嫌いだという発言のせいで、静的型反対派を信用できない側に立つことになる。コンピューターにプログラミングを手伝ってほしくないプログラマーは、非常に疑わしい
    • 象牙の塔や社会的圧力が、特定の技術導入をためらわせるという点は良い指摘だ
      ただし、それが技術的な利点を損なうわけではない。ある技術が優れていても、その周囲の人々が虚勢に満ちていることはあり得る
      動的型付けが単純なコードを書かせるという主張は、「目隠しをして運転すればゆっくり運転するようになるから良い」と言っているように聞こえる。そういう目的なら、行の長さや引数の数に制限をかけるリンターを使えばよく、間接的に制約を作る必要はない
      型が唯一の解決策ではないが、投資対効果が非常に大きいので、最初に取り出すべき道具だと思う。投資はほとんどなく、得られるものは大きい
      「コードがドキュメント」という言葉には同意するが、型もコードの一部だ。だから「コードがドキュメントであり、型はコードの一部だ」と表現したい
    • ほとんど同じ返答を書こうとしていたところだった
      私たちがいる領域は工学であり、唯一の正解はなく、すべてはトレードオフだ。むしろだからこそ、私たちの仕事がすぐに自動化されて消えるわけではない
      このスレッドで、ほかのエンジニアの意見や経験を理由に「見下す」ような雰囲気は本当に不快だ
  • ほとんどのデータがJSONとしてネットワークを行き来している状況で、強い静的型付けを適用する戦いは、たいてい一貫性なく行われている
    使える道具はすべて使うべきだが、ほとんどの「データ」は思っているよりずっと曖昧で柔らかい。人々が電話番号を文字列にしているのは怠けているからではなく、かつてもっと強い型にできると勘違いして痛い目を見すぎたからだ。名前、住所、郵便番号も同じだ
    こうした値はユーザーから受け取る必要があり、実質的にはテキストをパースする以外にない。パース前の元テキストを保存しないようなシステムを作ると、いつかほぼ必ず後悔する
    元の入力テキストを保持しつつ、バックエンドの利用者には型付きのデータ集合として提供する層を置くのが最善だと思うが、それぞれの小さな領域でそれだけの投資対効果があるかは検討すべきだ
    重い評価を行うなら、SATや他の数値モデルへ変換する層が必要になる可能性が高い。その世界では数値が抽象化だ。別のやり方をしようとすると、ほぼ必ず苦しむ。問題を定式化へ、解空間をドメインへ翻訳する層を置くのがよく、型はここで役に立ち得るが、実際に注目される「型」は、こうした型ではない場合があまりに多い

    • 筆者です。Svixでのやり方について、ある段落で少し触れただけで、もっと説明すべきだった点があります
      SerdeやPydanticのようなライブラリのおかげで、デシリアライズがそのまま検証という方式に従っています。JSONデータをコード上の構造体にする前に、すべて検証します
      Redisの例に似ていますが、ネットワーク経由でJSONを受け取っても、完全に検証したうえでコードに到達すれば、きちんと形式化された型だと安心できます。そのためコードでは、メール型は有効なメールであり、ID型は有効なIDだと仮定できます
    • 名前と住所は、深く見ればどちらも文字列であっても、それでも文字列ではなく別個の型として扱うべきだ
      名前フィールドと住所フィールドを混ぜるのはほぼ常にエラーであり、型システムはそれを強制できる
    • ほとんどのデータがJSONでやり取りされているからといって、本当にそこまで混乱するものだろうか。特定の場合にはMapも完全に合理的な型だ
  • タイプミスがランタイムエラーになるなら、それは「より速く動く」ことではないし、関数シグネチャを変えるたびにコードベースを grep してすべての呼び出し箇所を探し、全部直せたことを祈らなければならないなら、それは「より生産的」でもない
    型は良いものだが、何事も行き過ぎると問題になる。すべてのビジネスロジックを型システムにエンコードすることを人生の目標にすると、型がまったくない場合よりも理解不能な混乱が生まれる。エラーメッセージで型名が1行に収まらないなら、行き過ぎている

    • 狂気じみた TypeScript の型定義を見たことがある
      公平に言えば、古い純粋な JS コードに合わせるためのもので、その気の毒な変数にはあらゆる値が入り得た
      TypeScript には永遠に感謝しているが、将来「型が行き過ぎた時代」という論文にそんなコードが載っていても驚かないと思う
    • 以前は from pdb import set_trace: set_trace() で対話的に編集できるので、コンパイラより実行中のプログラムとやり取りするほうがよいと思っていた
      しかしプログラムが少しでも複雑になると状況は変わる。キューでシステム間のデータを渡し、非同期・スレッド・マルチプロセッシングを使い、性能の要所にコンパイル済みバイナリライブラリを使うようになると、結局すべて Erlang で書いていればよかったと感じるようになる
    • 関数シグネチャの変更を本当にそうやって作業している人たちがどう働いているのか、すごく気になる
      100% のコードカバレッジを持つ非常に厳格なテストのような、全体的な方法論があるのだろうか?
    • 静的型付けについてこういう不満を見るたびに、型定義とアーキテクチャをいったいどう作ったらこれが問題になるのか不思議に思う
      ビルド時やコンパイル時に呼び出し箇所を捕まえられないなら、静的型付けを使っているとは言えない
    • その2つが型の議論とどうつながるのか、よく分からない
      タイプミスは、最も強力な静的型付け言語でも間違ったコードを作り得る。そうでなければ、コードを書くとはいったいどういう意味なのか? ランタイムエラーと、エラーなしに間違った結果を出すことのどちらが悪いのか?
      Python プロジェクトの実行は C++ のコンパイルより速いこともあるし、動的型付け言語でも関数呼び出しを探すのに grep より優れた方法を提供できる
  • 型を使わなければ開発速度が速いという利点がある、という話も私の経験上は違う。静的型付けは日々のプログラミングを速くしてくれる
    IDE が静的型付けのおかげで良くなる点は後で触れたが、REPL でも実感する。静的に捕捉された型エラーは、実際の根本原因にずっと近い意味のあるエラーメッセージを出し、ランタイムエラーよりも早く修正できる
    型について過度に慎重に考えなければならない負担も減る。コンパイラが規律を守ってくれるので、自分はそれほど気にしなくてよい。大きな種類のエラーが即座に捕まるという確信があるので、より速く動ける
    私の経験では、静的型システムは使いやすく、開発を速くし、信頼性を高める。コストは今のところ2つだけだった。学ぶのがより難しい場合があることと、実装がより難しいことだ

    • 完全に同意する。保守性の議論もこれでほぼ片がつく
      6か月後に採用するジュニア開発者は、型のないコードに慣れるのにずっと時間がかかるだろう
      人によっては最初に書くのが「速い」かもしれない点は認めるが、その後そのコードを読むすべての開発者は遅くなる
    • まさにその慎重な思考こそが、ソフトウェアをよりきれいで良いものにすると主張することもできる
      コンパイラを通るだけのごちゃ混ぜではなく、正確に何を入れて何を出すのか、そしてなぜそうするのかを考えるのは良いことかもしれない
  • 筆者はほぼすべての点で間違っていると思う。私も何十年もそう考えていたが、ここ数年で完全に考えを変えた
    型がバグを減らす? いいえ。ごくわずかにはそうかもしれないが、有意ではない。関連研究を見ればよい
    型がより良い開発体験をもたらす? いいえ。私の REPL と IDE にはすべての定義と変数がある。すべてのシンボル補完、呼び出しツリー、使用箇所の探索、自信を持ったリファクタリング、関数の単独実行・置き換え・ラッピングを、REPL とアプリケーション内でできる
    すべてを型システムにエンコードする? 不可能だ。ランタイム検証が必要だ
    要件が変わったときに型定義をほどくのも、幸運を祈る。これが決定打だ。静的型付けは、現在理解しているドメインデータモデルをあまりに早く固めてしまう。そのモデルは変わるし、運が悪ければ同じランタイムの中で複数のドメインモデルの変種をサポートしなければならない。特に継承を使っていたなら、さらに苦労する
    静的型付けがコンパイラ最適化に大きなてこを与えるのは確かだが、動的型付け言語の中にも静的型付けをオプションの付加機能として提供する言語がある
    多くのユースケース、特にエンタープライズ開発では、動的型付けに不変性優先の関数型言語を組み合わせることが、長期的には大きな利益をもたらす
    強い型付けの支持者がよく犯すカテゴリーミスは、同じコードを型だけなしで書くと仮定することだ。実際にはそんなふうには書かない

    • 結局、誰もが自分の経験からまったく違う結論を引き出すので、必然的にもどかしい議論になる
      ほぼすべての点で、私の結論は正反対だ。ランタイム検証が必要だという話はもちろん正しいが、ランタイム検証の大半は避けられる
      要件変更については、静的型付けのほうがむしろ適応を容易にすると見ている。私が経験した動的型システムでは、データ構造に関する重要な仮定があちこちに散らばっており、ときには事前条件・事後条件として動的に検査されていたり、テストにしかなかったり、まったく検査されていなかったりした
      要件を変えるには、こうした暗黙の仮定への影響をすべて推論しなければならず、変更が怖かった。新しいコードでアプリを起動するのは簡単でも、思いもよらないまれなコードパスを壊したかどうかを知るのは非常に難しかった
      「このインターフェースを変えたが、ここのコードパスがその部分に依存していたことを知っているか?」と教えてくれる静的解析ステップのほうを、私はずっと好む。静的型付けだけが方法ではないが、同じ水準の動的検証とテストを備えるより負担ははるかに小さいと思う
    • REPL と IDE で定義・変数を見て補完・リファクタリングをするのは良いが、検査したいコードを実際に実行しているときにしか機能しない。私の経験では、その方式はうまくスケールしない
      型システムはランタイム検証をなくすわけではないが、正しく使えば劇的に減らしてくれる
      要件が変わったとき、コンパイラが再び動くようにするには何を直すべきかを正確に教えてくれるのが、いちばん良いところだ。動的言語で同じことをすると、自分で追跡し、単体テストが失敗するのを待ち、抜け落ちた経路がないことを祈らなければならない
    • 私はむしろこの部分が、静的型付けではるかに簡単だった
      特定の型が使われているすべての場所を、ずっと自信を持って見つけ、それぞれに変更が必要かどうかを確認できた。動的型付け環境では、この作業ははるかに細かく面倒だった
    • それはあなたの経験かもしれないが、私の経験では違う。誰にでも合う正解はないと思う。自分に合うものを選んで先に進めばいい
  • C++、Python、JSで数十万行を書いた開発者としても、正直よく分からない。それほど明確ではない
    3つとも生産的ではあるが、Pythonがたいてい勝つ。ただしゲームエンジンやビデオコーデックをPythonで書くつもりはない
    JavaScriptは一貫性がなく奇妙だが、Netscapeの遺産がすでに私たち全員をそこに縛り付けている
    非常にオブジェクト指向的で、巨大なネストクラスが多いスタイルでは、コンパイル・パース時点の静的型付けが多くのミスを減らしてくれる可能性がある。しかしオブジェクト指向は概して災厄に近いものだと見るようになり、単純な関数と構造化データの方が、ほぼ常に単純さと保守性で勝る
    最新の言語サーバーとIDEは、JS/Python開発中でも多くのタイピングミスを見つけてくれる。プログラミングの多くの部分にはうるさい方だが、静的型付け対動的型付けについては強い立場は持っていなかった。どちらにも成功したプロジェクトが数百万ある

    • C++は型システムを抜きにしても、全体として生産性を出すにはかなり荒削りな言語だ
      言語が提供できる最大の生産性向上はガベージコレクションだと思う。はるかに単純な文法と型を持つGoがどう比較されるかの方が気になる。Javaもより良いかもしれない。冗長ではあるが、たいていの作業での認知負荷はC++のごく一部にすぎない
    • Pythonでは主に関数型スタイルでコードを書くが、それでも型は大いに役立つ
      タイポも多いし、引数の順序もよく間違える。特に機械学習をやるとき、型は大きな助けになる。データ処理に30分かけた後で学習コードが落ちるのは、最も避けたいことだ
      Pythonの漸進的型付けは、素早いプロトタイピングと、関数が十分に成熟したときに型注釈を付けるやり方との間の、非常に良い中間地点だと思う
    • 数年前のWWDCのプロトコル指向プログラミング(Swift)の動画が気に入るかもしれない: https://www.youtube.com/watch?v=p3zo4ptMBiQ
  • 強い型が弱い型より優れているという議論には決着がついたが、静的型が動的型より優れているかどうかには決着がついていない
    静的型の支持者は、コンパイラが型の不変条件を検証して「正しさ」を確認すべきだと考え、動的型の支持者はそれは時間の無駄だと見る
    私は明らかに後者だ。コンパイラが確認できるのはプログラムの正しさではなく、型の正しさだけだからだ。型の正しさはプログラムの正しさに必要だが十分ではない。静的型の支持者はこの点を認められず、静的型が実際以上のことを保証すると錯覚している
    記事の birthdayGreeting の例を見てみよう。筆者は birthdayGreeting("John", "20")"20" が数値ではないため、静的型がバグを捕まえると喜んでいる。しかし birthdayGreeting(" ", 123) は捕まらない。" " は名前ではない。birthdayGreeting("Anna," -12335) も捕まらない。逆に birthdayGreeting("Anna" 4.5) は捕まるが、4.5 も年齢と見なせるので、むしろ間違っているとも言える
    これは重要だ。「型バグ」は捕まえるのが些細なほど簡単だが、意味論的バグは何年も潜んでいることがある。uint で保存した口座残高のオーバーフロー、特定の位置では素数でなければならないのにそうでない数、空であってはならないリストなどがそうだ。依存型でさえ、こうした不変条件を保証することはできない
    信じがたいなら、宇宙船の爆発や自動車事故を引き起こした大規模なバグを探してみればいい。私の知る限り、本当の型エラーが原因だったケースはなく、圧倒的多数は意味論的エラーだった
    [1] ほとんどの人は、型を少なくとも2つの軸、強い/弱い、静的/動的で見るべきだという点を理解せず、弱い型と動的型を混同し続けている。C は静的かつ弱い型で、Python は強い動的型、JavaScript は弱い動的型だ

    • 例のかなりの部分は、型システムによっては静的型で十分に捕まえられる。ただ、もっと重要なのは「型バグは捕まえるのが些細なほど簡単だ」という部分だ
      まさにそれが理由で、私は静的型の側にいる。あまりに些細なので、宣言的に、検査対象のコードのすぐ隣で、即時のフィードバックとともに、すべての呼び出し箇所とすべての部分式・文で処理できる
      型注釈が意味論やドメインロジックが正しいという意味ではなく、それは依然としてテストしなければならない。だが、関心のあるロジックと直交する何十もの些細なテストを置き換えられる。正直、そうしたテストを漏れなく書いている人はほとんどいない
    • 筆者です。これらの例は反例ではなく、むしろ私の要点を示していると思う
      記事の後半で、ユーザー入力のような場所で型を作るときに検証すると述べた。だから Name 型は常に有効で、" " は名前ではない。型が有効な名前を保証するので、私たちのコードベースでは確実に捕まる
      birthdayGreeting("Anna" 4.5)birthdayGreeting("Anna," -12335) は、JS では number が浮動小数点数なので実際には有効だ。ただし、記事を書いたときは整数を念頭に置いていた。TS より厳密な型、たとえば Rust が不変条件をよりよく定義する助けになる、もう一つの例だ
      要約すると、単純な例を示そうとして、普段のようにすべての型を厳密に定義しなかった。その結果、型で捕まえられたはずのバグがさらに露呈した
    • これらの例が興味深いのは、私には必要なのが常に有効な名前を表す Name 型と、常に有効な年齢を表す Age 型だからだと思える点だ
      検証をそれらの型のコンストラクタ1か所に置けば、birthdayGreeting のような複数のメソッドはその型の値を責任なしに使える
      型検査、あるいは少なくとも任意の型ヒントと静的解析なしに、このパターンをうまく実装する方法が分からない。代わりにすべてのメソッドで入力値を検証するのは負担が大きすぎるし、呼び出し元が有効な値を渡すと仮定したうえで、大事故が起きないようテストするのも満足できない
    • 宇宙船の爆発に触れられると、すぐに [1] が思い浮かぶ。この有名な事故は、一部の型に許容範囲をあらかじめ定義した型検査に関係しており、あなたの意見ともつながっている。birthdayGreeting が 1〜150 の範囲を受け取るようにする、といったことは ADA では簡単にできる
      宇宙船関連で公開されている問題の中には、よりよい型検査で捕まえられた可能性があるものもいくつかある。メートル法/ヤード・ポンド法の変換も、単位を型に入れればよい。ただし [2] の場合は、統合テスト側の誤りだった可能性が高い
      もちろん型検査がコード上のすべての問題、特にアルゴリズム上の問題を見つけられるわけではないし、テストの代わりにもならない。それでも開発段階での即時フィードバックと型ヒントは非常に貴重だ
      例では、名前文字列を人型やオブジェクトに変えることもできる
      [1]: https://en.m.wikipedia.org/wiki/Ariane_flight_V88
      [2]: https://en.m.wikipedia.org/wiki/Mars_Climate_Orbiter
    • コンパイラがプログラムの正しさではなく型の正しさだけを確認するということが、なぜ時間の無駄だという根拠になるのか分からない
      完璧な解決策はないが、価値のある解決策はたくさんある
  • この問題についてはかなりの収束がありました。今ではほとんどの言語が、文レベルである程度の型推論を提供しています。C++にも auto があります。
    そのおかげで、コード中の型に関するボイラープレートは大きく減りました。C++ の for 文で長いイテレータ型をすべて書かなければならなかった時代は過ぎました。
    関数宣言と構造体フィールドは、コードを読むために型情報が必要な場所です。プログラムが数百行を超えたり、開発者が1人を超えたりするなら、ある程度の注釈は必須です。
    主な反論は当然ながら Python と JavaScript のユーザーから出てきます。Python はかなり奇妙な助言的な型システムを後から付け足し、JavaScript は TypeScript を後から付け足しました。どちらも後付けの型システムであり、型付きコードと型なしコードが混在する環境で使われます。これはつらいものです。
    LISP も数十年前に “flavors” と Common LISP Object System で型システムを後から付け足しましたが、それも見栄えのよいものではありませんでした。教訓は、型システムを後から付け足すとぐちゃぐちゃになるということです。

    • Python の型システムは、状況を考えればかなりよくできていると思います。
      OptionalNone の使用前のチェックを強制することや、typing.Protocol による構造的サブタイピングなど、よい機能があります。Python が最初から型を念頭に置いて設計されていればもっとよかったでしょうが、既存の Python コードと統合し、どのコードも壊してはならないという要求を考えると、かなりうまくやっている方です。
      Python の静的型付けにおけるより大きな問題は、エコシステムと慣習です。特に多くの開発者が実質的にデータサイエンティストとして Python を使うようになって、さらに悪化しています。きちんとしたメソッドシグネチャを書くのが面倒で、*args/**kwargs を乱用します。
      メソッドが DataFrame や辞書を雑多なものを詰め込む袋のように受け渡すことは非常によくあります。メソッドがカラムやフィールドを追加・削除するため、コードを実行するか全行を読むまで、そのデータ袋に何が入っているのかわからないならボーナスポイントです。
      もちろん、ほぼどんな言語でも似たようなことはできます。C# ですべての型を dynamic にしたり、Go のメソッドがすべて interface{} を受け取るようにしたりすることも可能です。しかし Python は長い間こうしたアプローチを積極的に奨励してきましたし、今でも多くの初心者向けチュートリアルが「*kwargs を受け取れば関数シグネチャを変えなくてよい」という話を、ひどい罠ではなく、賢い人のための高度な機能であるかのように紹介しています。
    • Python と TypeScript の「型システム」は、漸進的で非グリーンフィールドな環境に型を導入するために設計されています。
      段階的なマイグレーションには不可欠であり、このように動作するのは完全に理解できます。
    • Flavors と CLOS は「型システム」ではありません。「オブジェクトシステム」であり、特に醜いものでもありません。
      Flavors は型システムのなかった Lisp に導入され、その後 CLOS は、すでに型システムを持っていた Lisp である Common Lisp に追加されました。
    • TypeScript の型システムは本当に驚くほど優れています。もっと多くの型システムがそれくらい表現力豊かであってほしいです。
  • 人はいつも、理性より感情的にしがみついているものに対して狂信的になってきました。
    「型はバグを減らす」という主張は、事実というより、もっともらしく感じられる主張です。
    https://blog.metaobject.com/2014/06/the-safyness-of-static-t...
    試みが足りなかったわけでもありません。その主張は反証されたと言ってもよいかもしれません。
    ただ個人的には静的型付けが好きです。主にドキュメンテーション効果のためで、偶然ではないのかもしれませんが、実際に堅固な実証的根拠がある肯定的な効果もそこだけです。

    • 「長く続くコードベースを変更するとき、型はバグを減らす」というのは、ほとんど議論の余地がないと思います。
      最初から正しいコードを書くことより、リグレッションを防ぐことの方が重要な場合もありますし、進化しないコードを見ているときにはその点を評価できません。
      具体的には、大規模な純粋 JavaScript プロジェクトでオブジェクトのフィールドを削除するのは本質的に地雷原であり、過去に多くのバグを生んできました。一方、完全な TypeScript プロジェクトでは、同じ変更を自信を持って行えます。
    • 非常に強い主張なので極端な例を見ると、形式検証された CompCert の中間段があります: https://users.cs.utah.edu/~regehr/papers/pldi11-preprint.pdf
      これは、非常に強い静的型付けがバグを減らすという実証的根拠と見なせると思います。
      結局、多くのコメントが述べているように、型は二分法的なイエス/ノーの問題ではなく、静的/動的、強い/弱いなど、複数の軸を持つ大きなスペクトラムです。型システム同士にも大きな違いがあり、人々がその型システムを問題に適用する方法にも大きな違いがあります。
      静的で強い型の言語でも、すべてを文字列で表現し、変換し続けることはできますが、それは実質的に動的型付け言語のように作業しているということです。逆に、型システムが与える道具を活用して正当な値を表すクラスを作り、重要な不変条件を主張すれば、利点を得られます。
    • 強い静的型付けはコードを自己文書化させるので、はるかに好みです。
      生産性は数倍ではなく、桁違いに上がります。C、C++、Java、Python、JavaScript、TCL など、このスペクトラムの広い範囲をカバーする複数の言語をかなり使ってきた立場から言っています。
      最近触っていないコード、それが現在のプロジェクトであれ依存関係であれ、そのコードを推論するのがずっと簡単になります。ある関数が返したオブジェクトで正確に何ができるのかを突き止めるために脇道へそれ続けなくてよいので、目の前の問題により集中できます。
      コンパイルが通ったときの気持ちのよい安堵感もありますが、それは副次的なものです。
    • 静的型付けの主張は、型が静的であるがゆえに型のバグを不可能にする、というものです。
      感情的な執着があるとすれば、「でもすべてのバグをなくせるわけじゃないだろ!」に直面したときの怒りくらいです。
    • 誰かが「感情的な論証は理性的な論証より早く作れる」と主張したら、どう感じるでしょうか。
      この論争はまさにそういう感じがします。
      ハッシュマップを Apple や String のように扱おうとしたときに、コンパイラが「だめ」と言ってくれるという、単純な合理性が欲しいのです。