プログラミング
関数引数は関数の色ではない
Function Arguments Are Not Function Colors (jerf.org)
要約
関数引数が「色」として扱われることがあるという議論に対し、筆者は、引数が単に呼び出しスタック全体に影響を及ぼす「色」とは異なり、変更が直接の呼び出し元にのみ伝播する「通常の変更」とは区別されるべきだと論じている。Goのcontextのような例は、変更の伝播範囲が限定的であるため「色」ではないと分析し、関数の変更が呼び出しスタック全体に影響を与える場合にのみ「色」と定義できると提唱している。
全文翻訳
ページコンテンツ
長年にわたり、関数カラーに関するオンラインでの様々な議論で、関数引数もカラーを構成しうるという、一見もっともらしい立場を主張する人々がいました。例えば、Go言語にはcontext.Contextという値があり、タイムアウト、キャンセル、そして様々な関数にスレッドされる少量のデータを管理します。これは、受信者が直接使用しなくても、単に渡すためだけに、関数間で渡されるように設計されています。一般的に、ある関数がコンテキストを使い始めると、それを使用する可能性のあるすべての呼び出し関数にそれをスレッドする必要があります。したがって、表面的には、これはカラーのように見えます。この特定の引数を手に入れると、将来のそのタイプのすべての関数をその引数で呼び出さなければならなくなります。これを拡張すると、すべての関数パラメータが「カラー」であると主張することになり、実際にそう主張する人もいます。
迅速ながらも重要な反論は、もしそうであれば、私たちはこのことについて全く議論していないだろうということです。もし非同期が、数千もの他のカラーのきらめく混沌とした虹の中の単なる一つの「カラー」に過ぎないのであれば、この「カラー」という概念は、プログラミング言語の関数が発明されてから50年以上経っても発明されなかったでしょう。一部の言語における非同期の動作には、まだ捉えて理解する価値のある、何か独特なものがあります。しかし、一つの例から状況を深く理解するのは困難です。ここでは、それを単一の例ではなく、基準に変えようと試みます。
カラーとしての変更依存グラフ形状
この問題を次のように表現しましょう。ある関数の属性を変更する必要があると仮定します。それは呼び出しスタックの4段下にある関数で、それが取るパラメータ、それが「非同期」であるかどうか、あるいは「ASMが含まれているかどうか」のような他の何か、あるいは関数の他のどのような属性であっても構いません。ローカル言語が関数に持たせることを許可する特性に応じて、関数へのあらゆる変更。この変更は、呼び出し元を変更する必要が生じる可能性があります。そこには3つの基本的なケースが現れます。
呼び出し元の変更は一切不要。
変更は直接の呼び出し元への変更を必要とする可能性がある。
変更はスタック内のすべての関数への変更を必要とする可能性がある。
グラフィカルに、以下のようになります。
カラーなし、関数パラメータの場合、「変更の必要性」の関係は次のようになります。
[Diagram 1: Function Parameter Change Propagation]
カラーの場合、次のようになります。
[Diagram 2: Color Change Propagation]
「カラー」と「通常の」変更の違いは、カラーの場合、スタックの最下位の関数への変更はその親に影響するだけではないということです。それは即座に、そして避けがたく、その上のすべての関数に影響を与え、したがって、それらすべてが変更に対応しなければなりません。通常の変更の場合、それは呼び出し元にのみ伝播します。その後、その呼び出し元に連鎖する可能性があります。もしそれがトップレベルの関数まで伝播した場合、図で示されているように、これは違いのない区別のように見えるかもしれません。しかし、鍵は、図が一般的なケースを示しているのではなく、最悪のケースを示しているということです。
実際には、関数はチェーンがスタックのトップに到達する前にほとんどの場合、チェーンを断ち切ります。どの関数も伝播する変更を「キャッチ」してカプセル化できるため、それより上の関数はそれを見ることがなくなります。これを、スタックを伝って変更をスレッドアップする頻度で測定できます…まあ…手作業でプログラミングしていた昔はそうでした。あなたがそれを行うこと、しかしそれはあなたが常にそれを行うことではなく、そしてあなたがトップのメイン関数までそれを頻繁に行うことではなかったことは確かです。
変更の分離
私たち人間は、ほとんどの関数変更に対して、上位の関数が下位の関数のすべての詳細を知る必要がないように分離する能力があるため、構造化プログラミングを広く使用しています。最上位の関数が、子関数が最終的に必要とする可能性のあるすべてを知る必要があるとしたら、それはどのようなものになるでしょうか?プログラム全体のすべての関数パラメータの変更が、何らかの方法で呼び出しスタック全体にわたって変更を強制するのでしょうか?明らかにこれは起こりません。それはあまりにも起こらないので、私が何を話しているのか想像することさえ難しいかもしれません。
例えば、私が常に言及しているGoのコンテキストの場合、Goは私の主要なプログラミング言語の一つであるだけでなく、この例が常に挙がるからです。どこからでも、任意の関数がcontext.Background()を呼び出して、退化したものの完全に機能するコンテキスト値を取得できます。したがって、コンテキストを取るように変更された関数は、context.Background()の結果を渡すだけで、他の通常の関数から呼び出すことができます。さらに、これは仮説的な状況ではありません。コンテキストを持たなかった関数にcontext.Contextパラメータを追加するときに、テストコードやコンテキストを気にしない他の呼び出し元を新しいパラメータに適応させるために、実際のコードでまさにそれを行うことは非常に一般的です。
したがって、この「連結リスト」の関係は、実際にはチェーンをメインに当たる前に断ち切る傾向があるだけでなく、非常に、非常に早期にチェーンを断ち切る傾向があります。変更を最後までスレッドアップする必要があるのは苦痛なので、私たちはそれを回避するための多くの方法を開発し、関数が提供するカプセル化を改善してきました。時々それを行う必要があるかもしれませんが、それは非常に認知的に利用可能で、覚えやすく、気づきやすい稀なイベントの種類ですが、その結果、あなたの記憶の中で完全に不釣り合いになっています。実際には、それは一般的なケースではなく、非常に稀なケースです。
通常の変更はカプセル化できます。非常に特定の変更は、何らかの理由でメインまで行く必要があるかもしれませんが、これは明らかに例外であり、それでも、プログラムの残りの部分は他の方法でカプセル化する能力を使用する可能性があります。カラー変更は、このカプセル化を逃れます。スタック内のそれより上のすべての関数は、その要件に一致しなければなりません。
カラーの定義
「カラー」の形状の依存関係を持つ関数へのあらゆる変更はカラーです。連結リストの形状を持つ関数へのあらゆる変更はカラーではありません。元の定義エッセイは非同期をその標準的な例として使用しましたが、それはすべての言語のすべての非同期が常にカラーであるという意味ではありません。言語の非同期サポートの具体性を調べる必要があります。多くの言語では、それはカラーではありません。
私は、Zigの現在の変更のいくつかにより、IOを行う関数は、渡すIO値によってカラーであるかそうでないかのどちらかであると思います。あるいは、むしろ、カラーは関数自体ではなく、IO値の属性です。非同期は、定義に適合するかどうかに基づいてカラーです。定義は、非同期が常にカラーであるということではありません。元のエッセイが扱っている標準的な「JavaScript非同期」の場合でさえ、それが常に私たちが望むほど明るく輝く線ではないという事実によって、さらに複雑になります。JavaScriptでは、非同期アクションを同期コンテキストで実行することも依然として可能です。それは、同時に実行されるべき他の非同期アクションのコストを伴います。私はこれを「全く非同期で実行されていない」と見なすでしょう。なぜなら、コストの驚異的な性質と、それが機能の基本的な機能をどのように破壊し、かろうじて機能するかに基づいています。しかし、あなたの経験値は異なるかもしれません。
すべての場合と同様に、詳しく見れば見るほど、それは二項関係ではなくスペクトラムになり、スペクトラムは当初思っていたよりも複雑になるようです。しかし、物事がスペクトラムであり、線を引くことが当初思っていたよりも複雑になるとしても、カラー変更と通常の変更の間には実際の違いがないという意味ではありません。
高度なトピック:部分的なカラーリング
高度な理解のために、この視点から、部分的なカラーリングも想像できます。Haskellは部分的なカラーリングの王様であり、それらを使用して、介入する関数がカラーを使用してそれらを何らかの方法で制約することにより、何ができるかについて強力な保証を行います。例えば、ソフトウェアトランザクションメモリ(STM)モナッドは、トランザクション値の作成を可能にすることによってソフトウェアトランザクションメモリを実装します。これらの値は、単に...