プログラミング
グローバルプロパティのためのローカル推論
Local Reasoning for Global Properties (tratt.net)
要約
AIがコード生成能力を高める中、ローカルなコード片は高品質でも、プログラム全体のグローバルな理解を必要とするコード生成には苦労する傾向があります。この記事では、ローカルな推論から驚くべきグローバルプロパティの保証を可能にするプログラミング言語設計の例として、Rustのマルチスレッドプログラミングにおけるデータ競合の静的エラー化に焦点を当て、そのメカニズムを解説しています。
全文翻訳
Home > Blog ≡ Home> Blog email Bluesky Mastodon Twitter Bluesky Mastodon X ローカル推論によるグローバルプロパティ RSSフィード: サイト全体 またはブログのみ 最近の投稿 ローカル推論によるグローバルプロパティ テストケースリデューサーは過小評価されているデバッグツール JITコンパイラをCインタプリタにレトロフィット PLISS 2026 ロンドンでの今後の講演 AsyncとFinaliserのデッドロック コンテキストがターミナルマウスのクリックにもたらすもの なぜ最初が重要なのか LLMインフレ Glove80とMaltronキーボードの比較 ブログアーカイブ 過去数年間、私は「AIは新しい種類のプログラミング言語から恩恵を受けるだろうか?」という質問をますます受けるようになりました。私の答えは「おそらくそうではない」であり、少なくともこれまでのところ、その答えはうまく当てはまっています。AIは今や、あなたや私が思いつくほぼすべてのプログラミング言語で大量のコードを生成できるようになっています。技術が進歩し、その特性が明らかになり始めた今、私の答えは変わりました。私の経験では、AIは、少なくとも現在のところ、しばしば高品質なローカル(例:関数)のコード片を生成しますが、プログラムのグローバルな理解を必要とするコードの生成を求められると、しばしば苦労します。これを最も簡単に確認できるのは、不必要な防御的チェックの蔓延です。これらは無害に見えますが、後でコードを読む人が発生すると考える状態の数を指数関数的に増加させる可能性があり、それが意味するすべての有害な影響があります。おそらくこの苦労はすぐに克服されるでしょうが、そうならない場合、私たちは再びプログラミング言語設計に助けを求めることになるかもしれません。この記事の目的は、プログラミング言語がどのように、あるいはどのようにあるべきか、この問題を解決しようとするかを具体的に予測しようとすることではありません1。むしろ、より基本的な質問に答えたいのです。ローカル推論が驚くべきグローバルプロパティの保証を私たちに与えることを可能にするプログラミング言語設計の良い例はありますか?背景 私はプログラミング言語でかなりの生計を立ててきたので、それらの重要性を増幅させることに個人的な関心があります。しかし、プログラミング言語が私たちの生産性や、私たちが作成するソフトウェアの信頼性にいくらかの影響を与えると信じていますが、それが大きな違いを生むという証拠はあまりありません。私は単に「違いを証明する良い実験ができなかった」という意味ではありません。それも真実です!むしろ、「良い」言語で多くの「良い」ソフトウェアが作成され、「悪い」言語で多くの「悪い」ソフトウェアが作成されてきました。使用された特定のプログラミング言語がそのような結果の主な影響要因であった可能性は低いです。これに対する最も単純な議論は、ユーザーが必要とするすべてを、理解可能で信頼性の高い方法で実行するソフトウェアを作成するには、困難なプログラミング言語機能の専門知識よりも共感が必要であるということです。より微妙な見方としては、私は以前、ソフトウェアの性質について私の考えを捉えようとしました。これは、プログラミング言語が全く違いを生み出さないと言っていると受け取られるべきではありません。私がアセンブラでのプログラミングからPythonやCのような「高水準」言語に移行したとき、私の生産性は大幅に向上し、より大きなソフトウェアに取り組むことができると感じました。理由は簡単です。アセンブリは、私がより重要な高水準の全体像を常に忘れてしまうほど、多くの低水準の詳細に対処することを強制します。私が作成できたソフトウェアの違いは劇的でした。残念ながら、そのような大きな改善が繰り返される可能性は低いことに徐々に気づきました。私は、ゆっくりと、そして不器用に、フレッド・ブルックスの「銀の弾丸はない」という議論を再発明していました。ソフトウェア生産性の過去の大きな進歩のほとんどは、厳しいハードウェア制約、扱いにくいプログラミング言語、機械時間の不足など、偶発的なタスクを不当に困難にしていた人工的な障壁を取り除くことから来ています。ソフトウェアエンジニアが現在行っていることのどれだけが、本質的なものではなく、偶発的なものに費やされていますか?それがすべての努力の9/10以上でない限り、すべての偶発的な活動をゼロ時間に縮小しても、桁違いの改善は得られません。例外それは、多くの、多くの年後の特定の文脈で、私が書く多くのソフトウェアの生産性に別の劇的な変化を経験したとき、私はあまりにも驚いて、ほとんどそれに気づかなかったことを意味しました。ついに気づき、他の人々にその違いを説明しようとしたとき、彼らも困惑しているようでした。その文脈とは?Rustでのマルチスレッドプログラミングです。その経験が、将来のプログラミング言語の最善の方向性に関する私の意見を形成しているので、Rustがマルチスレッドプログラミングをいかに容易にするかという深い何かがあることをあなたに納得させる必要があります。具体的な例から始めましょう。あなたが今読んでいるウェブサイトを構築するソフトウェアを、通常のシングルスレッドコードとして書きました。私が怠惰であり、私のウェブサイトはそれほど大きくないので、実行するたびにウェブサイト全体が再構築されます。しばらくすると、ソフトウェアがサイトを再構築するために必要な一時停止が長すぎて、一部のページ(この投稿のような!)の編集が非効率的になることがわかりました。すぐにいくつかのシングルスレッド最適化を行いましたが、十分ではありませんでした。次に、これをマルチスレッド化するように書き直せば、一時停止を許容可能なレベルにまで短縮できるだろうと推測しました。ほぼすべての他のプログラミング言語で、ソフトウェアをマルチスレッド化するように書き直すことは daunting task( daunting task)だったでしょう。実際、マルチスレッドに関する私の過去の経験は、デバッグが困難なクラッシュにすぐに遭遇するだろうことを示していました。そして、ほぼ間違いなく、数週間から数ヶ月にわたってつまずくべきそのような恐怖の長い尾があるでしょう。マルチスレッドプログラムを書こうとするのをやめたのには十分な理由があります!しかし、この特定のケースでは、パフォーマンスの問題を実際に解決した書き直しは、5分未満で完了しました。最初の試行で正しく実行され、正しく動作し続けています。そして、その両方がそうなるだろうという完全な自信がありました。これはどうして可能なのでしょうか?私はRustが大好きです。2015年から私のメイン言語です。しかし、それは完璧な言語ではありません。実際、私はその欠点について詳細に説明することで人々を退屈させることができますし、そうしてきました。しかし、マルチスレッドに関しては、私がかつて想像もできなかったことを行っています。データ競合(つまり、2つのスレッドが予期せず干渉できる、調整されていない読み取り/書き込み)が静的エラーになります。それは小さなことではありません。マルチスレッドプログラムを書こうとしたとき、データ競合は、これまでで最もエラーの原因でした2。Rustがデータ競合を防ぐ方法Rust3は、所有権型とSendおよびSyncトレイトの組み合わせによってデータ競合を防ぎます。Rustの仕組みを知っている場合は、このセクションをスキップできます。Rustを知らない場合は、これらの機能の概要を可能な限り簡潔に、可能な限り簡略化して説明します。所有権型に迷い込むことはできますが、私たちが知る必要があるのは、特定のオブジェクトにはそれを読み書きできる所有者がいること、そしてオブジェクトは他の所有者に移動できること、その時点で元の所有者はオブジェクトへのアクセスを失い、他の所有者がそれにアクセスできるようになるということです。Sendは「この構造体のインスタンスは現在のスレッドから別のスレッドに移動できる」(つまり、移動後、現在のスレッドはそのオブジェクトにアクセスできない)ことを意味します。Syncは「複数のスレッドが同時に構造体のインスタンスから読み取ることができる」ことを意味します。私たちの目的のために、Rustが構造体がSendおよび/またはSyncできるかどうかを自動的に判断し、それらのトレイトを自動的に実装してくれると仮定できます。この非常に単純なRustコードから始めましょう:fn main() { let x = vec![1, 2]; println!("{x:?}"); } vec!によって作成されたベクトルはVec型のインスタンスを作成します。これはSendを実装しています。したがって、ベクトルを別のスレッドに送信し、その別のスレッドにベクトルを出力させることができます:fn main() { let x = vec![1, 2]; std::thread::spawn(move || println!("{x:?}")).join().ok(); } std::thread::spawn(...)はRustで新しいスレッドを作成する方法です。move || ...は「クロージャ」(つまり匿名関数)です。