
2026/05/27 0:58
あなたの関数の色は何色ですか?(2015)
RSS: https://news.ycombinator.com/rss
要約▶
日本語訳:
元の要約は、すべての主要な要点を効果的に伝えています。改善の余地はなく、忠実かつ明瞭で簡潔です:
まとめ:
JavaScript 開発は、同期コードと非同期コードの間における根本的な二分割により、開発者に大きな苦痛をもたらしています。この複雑さは、厳格な色分けルールが冗長な構文を強制し、痛みを伴う依存関係を生成するという架空の言語を通じて示されており、これは「コールバック地獄」から Promise へ JavaScript が進化した経歴を反映しています。Promise を用い더라도、値を直接利用するために待機(await)を行わない限り直接使用できないため、クリーンな非同期ロジックを書くには特別なラッパーが必要となることが多くあります。一方、C# の
async-await メカニズムは、式のネスティングを可能にし、基盤にある複雑さを隠蔽することで、標準的な関数のように値を直接使用することを可能にします。また、Go、Lua、Ruby などの他の言語では、同期および非同期の操作を区別しない真の並行性をモデルとしたアプローチにより優れた結果が得られています。Java もまた、コードをこの 2 つの要素に分けることを避けています。この分離は現在、困難なエラー処理を通じて生産性を低下させ、同期表現内の非同期ロジックを組み込むのに容易さを制限しています。統合された並行性モデルへの移行は、API デザインの単純化をもたらすだけでなく、維持可能なアーキテクチャで企業が苦闘している样板コードを大幅に削減することが可能です。本文
プログラミングにおける「赤と青」の罠:非同期処理の苦しみを告発する記事の日本語化と整理
序論:古風な言語への愛とリスク
- 古風なプログラミング言語への熱狂が最も心を奮い立たせます。
- 「ブルーブル(Blub)」批判のような大衆向け言語への辛辣な言及は、スタックオーバーフローへの畏敬と交わりつつあります。
- Chisel-sharp ツールという開化的な言語こそが、熟練の職人のためのものですが、激しい評論にはリスクが伴います。
- 皮肉を浴せた言語が読者の好物である場合、群衆による怒りや暴徒化(ピッチフォークと火把)を招く可能性があり、**刎首(すわぎ)**となる懸念があります。
新しい言語:A-NEW-LANGUAGE
ブログ記事のための新しい言語は、既存の JavaScript 的な文法(中かっこ、セミコロン、
if、while など)を採用し、以下の機能を備えます。
基本的な構文と機能
function thisIsAFunction() { return "It's awesome"; }
-
ファーストクラス関数をサポートしています。
-
**高階関数(Higher-order functions)**を採用し、例として
関数を定義します。filter// collection の predicate に一致する要素をすべて含むリストを返す function filter(collection, predicate) { var result = []; for (var i = 0; i < collection.length; i++) { if (predicate(collection[i])) result.push(collection[i]); } return result; } -
テストフレームワークやデータパースでも同様に活用されます。
describe("An apple", function() { it("ain't no orange", function() { expect("Apple").not.toBe("Orange"); }); }); tokens.match(Token.LEFT_BRACKET, function(token) { // リテラルリストをパース... tokens.consume(Token.RIGHT_BRACKET); }); -
関数の受け渡しと再利用はFunctapaloozaと呼ばれるほど素晴らしいものです。
あなたの関数はどの色ですか?(WHAT-COLOR-IS-YOUR-FUNCTION)
しかし、この言語には狂気のような特徴があります:すべての関数には色が付けられています。赤か青かの二択です。
基本ルール
- 色の指定:単一の
キーワードではなく、以下のどちらかを選択します。functionblue_function doSomethingAzure() { // 青い関数... } red_function doSomethingCarnelian() { // 赤い関数... } - 呼び出し方:関数の色に応じた構文が必要です。
doSomethingAzure()blue; // 青い関数の呼び出し doSomethingCarnelian()red; // 赤い関数の呼び出し
追加の制約とリスク
-
色ミスの結果:色の組み合わせを間違えると、悪夢のようなエラーが発生します(例:赤い関数を
で呼ぶ)。blue -
相互呼び出しの制限:
- ✅ 赤い関数の中に青い関数を呼ぶことは可能です。
red_function doSomethingCarnelian() { doSomethingAzure()blue; } - ❌ 逆は不可。青い関数の中に赤い関数を呼ぶと「ナイト・クラウン」スパイダーマウス級の恐怖が訪れます。
blue_function doSomethingAzure() { doSomethingCarnelian()red; // 禁止! }
- ✅ 赤い関数の中に青い関数を呼ぶことは可能です。
-
高階関数の困難さ:
などの高階関数を定義する際に、色の選定が難しくなります。filter()- 解決策として
を赤く設定し、双方を受け入れることができますが、新たな制約が生じます。filter()
- 解決策として
-
赤い関数のコスト:
- 呼び出すたびに**痛み(面倒なループや文脈の制限)**を伴います。
- API を利用する全員が「唾を吐きかけ」るほどの不快感を引き起こします。
-
コアライブラリの問題:組み込みのコア関数の一部が赤(非同期)であるため、回避できません。
問題の正体は関数型プログラミングの方です!(ITS-FUNCTIONAL-PROGRAMMINGS-FAULT)
この「色の制約」の本質的な原因は、高階関数や再利用可能な関数の必要性にあります。
- 高階関数の多様性問題:
- 関数の色に対する多態性が必要であり、理屈に苦しむ必要があります。
- グラフ上のソーシャルネットワーク(ダイクストラのアルゴリズムなど)を独立した関数として再利用する際、呼び出し元の色の統一が困難です。
- 開発の阻害要因:
- 常に「色」について考える必要があり、それは開発における砂のように邪魔な存在となります。
色彩豊かな寓話(A-COLORFUL-ALLEGORY)
この「色」という寓話は、文学的なトリックであり、実際には人種差別や非同期処理の苦しみを表しています。
赤い関数とは何か? 赤い関数は非同期(asynchronous)なものです。
Node.js で JavaScript を書く場合、コールバックを伴う関数はすべて「赤い関数」です。前述のルールは以下の非同期処理の特性に完全に対応しています。
非同期関数の特性(=赤い関数の特性)
- 返値の違い:値を返しず、代わりにコールバックを呼び出します。
- 結果の与え方:
ではなく、渡されたコールバックを通じて結果を与えます。return - 相互呼出し禁止:同期関数から直接非同期関数(赤)を呼ぶとエラーになります(結果が確定していないため)。
- 制御フローの制限:式内の構成にできず、
や多くの制御フロー文法と併用できません。try/catch - コアライブラリの性質:Node の標準機能はすべて非同期です(※最近は
版も出てきました)。___Sync() - コールバック地獄:面倒な構造が「赤い関数」の存在そのものによって発生します。
私は未来の方が良くなることを約束します(I-PROMISE-THE-FUTURE-IS-BETTER)
コミュニティはこれらの問題を解決するために「プロミス(Promises)」や「フューチャー(Futures)」を導入しました。
- プロミスの役割:崩れたコールバックとエラーハンドラをラップした、非同期操作を表すファーストクラスオブジェクトです。
- 限界:
- 規則 #4(呼び出しの痛み)は緩和されますが、根本的な制約は残っています。
- 例外処理や制御フロー文法との併用は依然として困難です。
- 同期的コードからプロミス/フューチャーを返すことも不可能(またはメンテナンスの悪化を招きます)。
結論:
- プロミス/フューチャーを導入しても、言語は非同期と同期の二分割のままであり、開発者の不幸は続きます。
- 現状では、顔立ちは「刎首」そのものです。
私は解決策を待ち望んでいます(IM-AWAITING-A-SOLUTION)
C# は
async-await キーワードを導入し、非同期処理を同期的ように書けるようになりました。Dart もこれを採用しました。
Async-Await の効果
- 同期的な書き方で非同期呼び出しが可能になり、例外処理や制御フローにも埋め込みやすくなりました。
- 規則 #4 の解決:赤い関数を呼ぶ際の「痛み」を軽減しました。
しかし残る問題点
- 二つの色は依然として存在します:
- 同期関数(値を返す)
- 非同期関数(
やTask<T>
を返す)Future<T>
- 呼出し方の変化:
- 同期関数:単に呼び出す。
- 非同期関数:
が必要。await - 結果を受け取るためには、ラッパーオブジェクトを「解凍(await)」する必要があります。
根本的な解決に至らない理由
はコンパイラが CPS(継続パススタイル)変換を行うのを助けますが、完全な解消ではありません。Async-await- コードベース全体に「色」による概念が滲み込みます。高階関数や再利用を試みる限り、この問題は再発します。
どの言語は色がありませんか?(WHAT-LANGUAGE-ISNT-COLORED)
JS, Dart, C#, Python および CoffeeScript など、非同期 IO を持つ多くの言語はこの「色の問題」を抱えています。 一方、Javaは非同期処理に慎重に進化しており、Go, Lua, Ruby などは根本的に問題がありません。
共通点は何か?スレッド(Thread)
- 切り替え可能な複数の独立したコールスタックです。
- OS スレッドでなくても良い場合:
- Go: ゴルーチン (Goroutines)
- Lua: コルーチン (Coroutines)
- Ruby: ファイバー (Fibers)
これらのアーキテクチャにより、IO 待機時でも他の処理を継続でき、「同期」と「非同期」の境界線がなくなります。C# はスレッドを活用することで非同期の痛みを回避しています。
過去の操作への回顧(REMEMBRANCE-OF-OPERATIONS-PAST)
根本的な問題は**「操作完了後の位置情報をどう引き継ぐか」**にあります。
- 通常のプロセス:
- コールスタックに現在地を保持し、関数呼び出しを追跡します。
- 非同期 IO を実行すると、OS レベルでの待機が必要になり、コールスタック全体がアンワインド(破棄)されます。
- 解決策としての CPS:
- Node.js はクロージャを使ってすべてのコンテキストを保存し、結果を渡す仕組みを作りました。
- これを**継続パススタイル(CPS)**と呼びます。
- コードが複雑化し、人間が手動でクロージャを具体化する必要があります。
プロミスやフューチャーも同じ問題
- これらは巨大な関数リテラルの山を手作業で作成する必要があるため、根本的な解決になっていません。
生成された解決策を待っています(AWAITING-A-GENERATED-SOLUTION)
Async-await はコンパイラが CPS 変換を行うために await を挿入点として利用しますが、完全な解放ではありません。
- ジェネレータとの類似:
キーワードを持つジェネレータは、同様の手法でゲームループなどを実装できます。yield- しかし、結局のところ関数にはヒープ上の一連のクロージャが塗られています。
- スタックアンワインドの必須性:
- どのアプローチであっても、非同期処理完了時にはコールスタックを再構築する必要があります。
- これが「赤い関数は赤い関数のみから呼べる」という制約の正体です。
具現化されたコールスタック(REIFIED-CALLSTACKS)
解決策は明確です:**スレッド(グリーンまたは OS レベル)**を使用することです。
- Go のアプローチ:
- IO 操作で待機しても、そのスレッド全体を停止せず、他のタスクに切り替えます。
- 「同期」と「非同期」の区別自体が存在しません。
- このため、前述の 5 つの規則による痛みは完全に消えます。
結論
- 新しい熱い言語や非同期 API を紹介された時、再び「赤い関数」と「青い関数」の話になるのを避けるために:スレッドベースのアプローチを選ぶべきです。