スコープ(コンピューターサイエンス)

ウィキペディアから、無料の百科事典
ナビゲーションにジャンプ 検索にジャンプ

コンピュータプログラミングは、名前バインディングのスコープ変数などのエンティティへの名前の関連付け)は、名前バインディングが有効なプログラムの一部です。つまり、名前を使用して、実在物。プログラムの他の部分では、名前が別のエンティティを参照している場合(別のバインディングを持っている場合)、またはまったく参照していない場合(バインドされていない場合があります)。名前バインディングのスコープは、特に古いまたはより技術的な文献では、エンティティの可視性としても知られています。これは、参照する名前ではなく、参照されるエンティティの観点からのものです。

「スコープ」という用語は、プログラムの一部内またはプログラム内の特定のポイントで有効なすべての名前バインディングのセットを指すためにも使用されます。これは、より正確にはコンテキストまたは環境と呼ばれます。[a]

厳密に言えば[b]、実際にはほとんどのプログラミング言語で、「プログラムの一部」はソースコードの一部(テキストの領域)を指し、字句スコープとして知られていますただし、一部の言語では、「プログラムの一部」は実行時間の一部(実行中の期間)を指し、動的スコープと呼ばれます。これらの用語はどちらも多少誤解を招く可能性があります。定義で説明されているように、技術用語を誤用していますが、区別自体は正確で正確であり、これらは標準のそれぞれの用語です。字句スコープはこの記事の主な焦点であり、動的スコープは字句スコープとは対照的に理解されます。

ほとんどの場合、字句スコープに基づく名前解決は、使用および実装が比較的簡単です。使用中は、ソースコードを逆方向に読み取って、名前が参照するエンティティを判別でき、実装では、名前のリストを維持できます。プログラムをコンパイルまたは解釈するときのコンテキスト。名前のマスキング前方宣言、および巻き上げで問題が発生しますが、特にクロージャでは、非ローカル変数でかなり微妙な問題が発生します

定義

名前(識別子)の(字句)「スコープ」の厳密な定義は明確です。字句スコープは、「名前とエンティティのバインドが適用されるソースコードの部分」です。これは、 ALGOL60の仕様における1960年の定義から実質的に変更されていません代表的な言語仕様は次のとおりです。

ALGOL 60(1960)[1]
次の種類の数量が区別されます:単純変数、配列、ラベル、スイッチ、およびプロシージャ。数量のスコープは、その数量に関連付けられたIDの宣言が有効であるステートメントと式のセットです。
C(2007)[2]
識別子はオブジェクトを示すことができます。機能; タグまたは構造体、共用体、または列挙型のメンバー。typedef; ラベル名; マクロ名; またはマクロパラメータ。同じ識別子は、プログラムのさまざまなポイントにあるさまざまなエンティティを示すことができます。[...]識別子が指定するさまざまなエンティティごとに、そのスコープと呼ばれるプログラムテキストの領域内でのみ識別子が表示されます(つまり、使用できます) 。
行く(2013)[3]
宣言は、空白以外の識別子を定数、型、変数、関数、ラベル、またはパッケージにバインドします。[...]宣言された識別子のスコープは、識別子が指定された定数、型、変数、関数、ラベル、またはパッケージを示すソーステキストの範囲です。

最も一般的な「スコープ」とは、特定の名前が特定の変数を参照できる場合(宣言が有効な場合)を指しますが、関数、型、クラス、ラベル、定数、列挙型などの他のエンティティにも適用できます

語彙スコープと動的スコープ

スコープの基本的な違いは、「プログラムの一部」の意味です。字句スコープ静的スコープとも呼ばれます)を持つ言語では、名前解決はソースコード内の場所と、名前付き変数または関数が定義されている場所によって定義される字句コンテキスト静的コンテキストとも呼ばれます)に依存します。対照的に、動的スコープを持つ言語では、名前解決は、実行コンテキストランタイムコンテキスト呼び出しコンテキスト、または動的コンテキストとも呼ばれます)によって決定される名前が検出されたときのプログラムの状態に依存します。)。実際には、字句スコープを使用すると、ローカルの字句コンテキストを検索して名前を解決し、それが失敗した場合は、外側の字句コンテキストを検索します。一方、動的スコープでは、名前はローカル実行コンテキストを検索することによって解決され、それが失敗した場合は、外部実行コンテキストを検索するなどして、呼び出しスタックを上に進めます。[4]

最近のほとんどの言語は変数と関数にレキシカルスコープを使用しますが、動的スコープは一部の言語、特にLispの一部の方言、一部の「スクリプト」言語、および一部のテンプレート言語で使用されます。[c] Perl 5は、字句スコープと動的スコープの両方を提供します。字句スコープの言語でも、クロージャのスコープは、クロージャが呼び出される場所ではなく、クロージャが定義される字句コンテキストに依存するため、初心者には混乱を招く可能性があります。

字句解像度はコンパイル時に決定でき、アーリーバインディングとも呼ばれますが、ダイナミック解像度は一般に実行時にのみ決定できるため、遅延バインディングと呼ばれます。

関連する概念

オブジェクト指向プログラミングでは動的ディスパッチは実行時にオブジェクトメソッドを選択しますが、実際の名前バインディングがコンパイル時に実行されるか実行時に実行されるかは言語によって異なります。事実上の動的スコープは、名前解決を直接行わず、代わりにその場で拡張する マクロ言語で一般的です。

AngularJSなどの一部のプログラミングフレームワークでは、「スコープ」という用語を使用して、この記事での使用方法とはまったく異なるものを意味しています。これらのフレームワークでは、スコープは、変数に字句スコープを使用する言語で動的スコープをエミュレートするためにフレームワークによって特定の方法で使用される、使用するプログラミング言語(AngularJSの場合はJavaScript )のオブジェクトにすぎません。これらのAngularJSスコープは、他のオブジェクトと同様に言語の可変スコープの通常のルールに従い、独自の継承トランスクルージョンルール。AngularJSのコンテキストでは、混乱を避けるために「$ scope」(ドル記号付き)という用語が使用されることがありますが、変数名でドル記号を使用することは、スタイルガイドによって推奨されないことがよくあります。[5]

を使用

スコープは名前解決重要な要素であり[d]、これは言語セマンティクスの基本です。名前解決(スコープを含む)はプログラミング言語によって異なり、プログラミング言語内ではエンティティのタイプによって異なります。スコープのルールは、スコープルール(またはスコープルール)と呼ばれます名前空間とともに、スコープルールはモジュラープログラミングで重要であるため、プログラムの一部を変更しても、無関係な部分が壊れることはありません。

概要

スコープについて説明する場合、スコープ、 範囲、コンテキストという3つの基本的な概念があります。特に「スコープ」と「コンテキスト」はしばしば混同されます。スコープは名前バインディングのプロパティですが、コンテキストはプログラムの一部のプロパティであり、ソースコードの一部(字句コンテキストまたは静的コンテキスト)または実行時の一部実行コンテキスト、 実行時コンテキスト、 呼び出しコンテキスト、または動的コンテキスト)。実行コンテキストは、(現在の実行ポイントでの)字句コンテキストと、呼び出しスタックなどの追加のランタイム状態で構成されます[e]厳密に言えば、実行中にプログラムはさまざまな名前バインディングのスコープに出入りし、実行のある時点で名前バインディングは「コンテキスト内」または「コンテキスト外」であるため、名前バインディングは「コンテキストに入る」または「コンテキスト外に出る」 "プログラムの実行がスコープに出入りするとき。[f]ただし、実際の使用法ははるかに緩いです。

スコープはソースコードレベルの概念であり、名前バインディング、特に変数または関数の名前バインディングのプロパティ(ソースコード内の名前はプログラム内のエンティティへの参照です)であり、言語のコンパイラまたはインタプリタの動作の一部です。 。そのため、スコープの問題は、プログラムでより一般的に使用される一種の参照であるポインターに似ています。名前がコンテキスト内にあるが変数が初期化されていないときに変数の値を使用することは、ワイルドポインターが未定義であるため、参照解除(の値へのアクセス)に似ています。ただし、変数はコンテキストから外れるまで破棄されないため、ダングリングポインターの類似物は存在しません。

変数などのエンティティの場合、スコープは存続期間のサブセット(エクステントとも呼ばれます)です。名前は存在する変数(おそらく未定義の値)のみを参照できますが、存在する変数は必ずしも表示されません。変数は存在する可能性がありますが、アクセスできない(値は格納されているが、指定されたコンテキスト内で参照されていない)、またはアクセスできるが指定された名前を介してアクセスできない(この場合、コンテキスト内にない(プログラムは「名前の範囲外」)。その他の場合、「ライフタイム」は関係ありません。ラベル(ソースコード内の名前付き位置)のライフタイムはプログラム(静的にコンパイルされた言語の場合)と同じですが、プログラム内の特定の時点でコンテキスト内にあるかどうかに関係なく、同様に静的変数静的グローバル変数はプログラム全体のコンテキストにあり、静的ローカル変数は関数または他のローカルコンテキスト内のコンテキストにのみ存在しますが、どちらもプログラムの実行全体の存続期間があります。

名前がどのエンティティを参照するかを決定することは、名前解決または名前バインディング(特にオブジェクト指向プログラミングでは)と呼ばれ、言語によって異なります。名前を指定すると、言語(適切には、コンパイラーまたはインタープリター)は、コンテキスト内にあるすべてのエンティティーの一致をチェックします。あいまいな場合(同じ名前のグローバル変数とローカル変数など、同じ名前の2つのエンティティ)、名前解決ルールを使用してそれらを区別します。ほとんどの場合、名前解決は、Python LEGB(ローカル、囲み、グローバル、組み込み)ルールなどの「内部から外部へのコンテキスト」ルールに依存します。名前は、最も狭い関連コンテキストに暗黙的に解決されます。場合によっては、名前解決を明示的に指定できますglobalnonlocalPythonのキーワード; その他の場合、デフォルトのルールをオーバーライドすることはできません。

2つの同一の名前が同時にコンテキスト内にあり、異なるエンティティを参照している場合、優先度の高い名前(通常は最も内側)が優先度の低い名前を「マスキング」している名前マスキングが発生していると言います。変数のレベルでは、これは変数シャドウイングとして知られています。マスキングによる論理エラーの可能性があるため、一部の言語ではマスキングを禁止または禁止し、コンパイル時または実行時にエラーまたは警告を発生させます。

さまざまなプログラミング言語には、さまざまな種類の宣言と名前に対してさまざまなスコープルールがあります。このようなスコープルールは、言語のセマンティクスに大きな影響を与え、その結果、プログラムの動作と正確性に大きな影響を与えます。C ++のような言語では、バインドされていない変数にアクセスすることは明確に定義されたセマンティクスを持たず、ダングリングポインターを参照するのと同様に、未定義の動作を引き起こす可能性があります。スコープ外で使用される宣言または名前は、構文エラーを生成します。

スコープは他の言語構造に関連付けられ、暗黙的に決定されることがよくありますが、多くの言語は、スコープを制御するための構造も提供しています。

スコープのレベル

スコープは、単一の式からプログラム全体までさまざまで、その間に多くの可能なグラデーションがあります。最も単純なスコープルールはグローバルスコープです。すべてのエンティティはプログラム全体で表示されます。最も基本的なモジュラースコープルールは2レベルのスコープであり、プログラム内の任意の場所にグローバルスコープがあり、関数内にローカルスコープがあります。より洗練されたモジュラープログラミングでは、名前がモジュール内(モジュール専用)には表示されますが、モジュール外には表示されない、個別のモジュールスコープが可能です。関数内では、Cなどの一部の言語では、ブロックスコープでスコープを関数のサブセットに制限できます。その他、特に関数型言語では、式のスコープを使用して、スコープを単一の式に制限できます。他のスコープには、モジュールスコープと同様に動作するファイルスコープ(特にC)が含まれます。

微妙な問題は、スコープがいつ開始および終了するかということです。Cなどの一部の言語では、名前のスコープは名前宣言で始まるため、特定のブロック内で宣言された異なる名前は異なるスコープを持つことができます。これには、必ずしも関数を定義する必要はありませんが、使用前に関数を宣言する必要があり、場合によっては、特に相互再帰のために前方宣言が必要です。Pythonなどの他の言語では、名前のスコープは、名前が定義されている場所(関数の開始など)に関係なく、名前が宣言されている関連ブロックの先頭から始まります。したがって、特定のブロック内のすべての名前には、同じスコープ。JavaScriptでは、で宣言された、letまたは名前宣言でconst始まる名前のスコープ、およびで宣言された名前のスコープvar名前が宣言されている関数の先頭から始まります。これは、変数ホイストと呼ばれます。未定義の値を持つコンテキストでの名前の動作は異なります。Pythonでは未定義の名前を使用するとランタイムエラーが発生しますが、JavaScriptでは、で宣言された未定義の名前はvar値に暗黙的にバインドされるため、関数全体で使用できますundefined

式スコープ

名前バインディングのスコープは式であり、式スコープと呼ばれます式のスコープは、多くの言語、特に宣言のスコープを単一の式にすることができるlet-expressionsと呼ばれる機能を提供する関数型言語で使用できます。これは、たとえば、計算に中間値が必要な場合に便利です。たとえば、Standard MLでは、 12返す場合、は144と評価される式であり、呼び出しを回避するためにxという名前の一時変数を使用します。f()let val x = f() in x * x endf()2回。ブロックスコープを持つ一部の言語は、式に埋め込まれるブロックの構文を提供することにより、この機能を近似します。たとえば、前述のStandard ML式は、Perlでとして、またはGNUCとして記述できますdo { my $x = f(); $x * $x }({ int x = f(); x * x; })

Pythonでは、ジェネレータ式とリスト内包表記(Python 3の場合)の補助変数に式のスコープがあります。

Cでは、関数プロトタイプの変数名には式スコープがあり、このコンテキストでは関数プロトコルスコープと呼ばれます。プロトタイプの変数名は参照されないため(実際の定義では異なる場合があります)、これらは単なるダミーです。たとえば、ドキュメントの生成に使用される場合もありますが、これらは省略されることがよくあります。

ブロックスコープ

名前バインディングのスコープはブロックであり、これはブロックスコープと呼ばれますブロックスコープは、すべてではありませんが、多くのブロック構造化プログラミング言語で使用できます。これはALGOL60で始まり、「[e]すべての宣言...はそのブロックに対してのみ有効です。」[6]そして今日は特にPascalおよびCファミリーの言語と伝統に関連付けられています。ほとんどの場合、このブロックは関数内に含まれているため、スコープが関数の一部に制限されますが、Perlなどの場合、ブロックが関数内にない場合があります。

unsigned int sum_of_squares const unsigned int N {      
  unsigned int ret = 0 ;    
  for unsigned int n = 1 ; n <= N ; n ++ {          
    const unsigned int n_squared = n * n ;       
    ret + = n_squared ;  
  }
  retを返す; 
}

ブロックスコープの使用の代表的な例は、ここに示すCコードです。ここでは、2つの変数がループにスコープされています。1回初期化されてループの反復ごとにインクリメントされるループ変数nと、補助変数n_squaredです。反復ごとに初期化されます。目的は、特定のブロックにのみ関連する変数を関数スコープに追加しないようにすることです。たとえば、これにより、汎用ループ変数iが誤って別の値に設定されている場合のエラーを防ぐことができます。この例では、式n * nは通常、補助変数に割り当てられず、ループの本体は単純に記述されますret += n * nが、より複雑な例では、補助変数が役立ちます。

ブロックは主に、if、while、forループなどの制御フローに使用されます。これらの場合、ブロックスコープは、変数のスコープが関数の実行フローの構造に依存することを意味します。ただし、ブロックスコープを持つ言語では、通常、「ネイキッド」ブロックの使用も許可されます。その唯一の目的は、可変スコープのきめ細かい制御を可能にすることです。たとえば、補助変数をブロックで定義してから使用し(たとえば、関数スコープで変数に追加)、ブロックの終了時に破棄したり、whileループをブロック内で囲んでループ内で使用される変数を初期化したりできます。これは一度だけ初期化する必要があります。

Algol 68やC(この例で示され、C99以降で標準化されている)などのいくつかのプログラミング言語の微妙な点は、ブロックスコープ変数をブロックの本体内だけでなく、制御ステートメント内でも宣言できることです。どれか。これは、関数宣言(関数本体のブロックが開始する前)および関数本体全体のスコープで宣言される関数パラメーターに類似しています。これは主にforループで使用されます。forループは、whileループとは異なり、ループ条件とは別の初期化ステートメントを持ち、一般的なイディオムです。

ブロックスコープはシャドウイングに使用できます。この例では、ブロック内で補助変数をnと呼び、パラメーター名をシャドウイングすることもできますが、エラーが発生する可能性があるため、これは不適切なスタイルと見なされます。さらに、JavaやC#などのCの子孫の中には、ブロックスコープをサポートしているにもかかわらず(ローカル変数を関数の終了前にコンテキストから外すことができるという点で)、あるローカル変数が別のローカル変数を非表示にすることを許可しないものがあります。 。このような言語では、2番目のnを宣言しようとすると構文エラーが発生し、n個の変数の1つを名前変更する必要があります。

ブロックを使用して変数の値を設定する場合、ブロックスコープでは、変数をブロックの外部で宣言する必要があります。これにより、単一の割り当てでの条件ステートメントの使用が複雑になりますたとえば、ブロックスコープを使用しないPythonでは、変数を次のように初期化できます。

if  c 
    a  =  "foo" 
else 
    a  =  ""

ステートメント aの後にアクセスできる場所。if

ブロックスコープを持つPerlでは、代わりにブロックの前に変数を宣言する必要があります。

私の $ a ; 
if  c  { 
    $ a  =  'foo' ; 
}  else  { 
    $ a  =  '' ; 
}

多くの場合、これは代わりに複数の割り当てを使用して書き直され、変数をデフォルト値に初期化します。Python(不要な場合)では、これは次のようになります。

a  =  "" 
if  c 
    a  =  "foo"

Perlの場合、これは次のようになります。

私の $ a  =  '' ; 
if  c  { 
    $ a  =  'foo' ; 
}

単一の変数割り当ての場合、代わりに三項演算子を使用してブロックを回避することもできますが、これは一般に複数の変数割り当てでは不可能であり、複雑なロジックでは読み取るのが困難です。

文字列の初期化ではメモリが自動的に割り当てられますが、すでに初期化された変数への文字列の割り当てには、メモリの割り当て、文字列のコピー、およびこれらが成功することの確認が必要になるため、これはC、特に文字列の割り当てでより重要な問題です。

{
  私の $ counter  =  0 ; 
  サブ increment_counter  { 
      return   ++ $ counter ; 
  } 
}

一部の言語では、ブロックスコープの概念を、関数の外部でさまざまな範囲で適用できます。たとえば、右側のPerlスニペットでは、$counterはブロックスコープを持つ変数名(myキーワードの使用による)であり、increment_counterはグローバルスコープを持つ関数名です。を呼び出すたびに、の値が1ずつincrement_counter増え、新しい値が返されます。$counterこのブロックの外側のコードはを呼び出すことincrement_counterができますが、それ以外の場合はの値を取得または変更することはできません$counterこのイディオムにより、Perlでクロージャを定義できます。

関数スコープ

関数内で宣言された変数のスコープがその関数を超えない場合、これは関数スコープと呼ばれます。[7]関数スコープは、関数またはサブルーチンでローカル変数を作成する方法を提供するほとんどのプログラミング言語で使用できます。関数が戻るとスコープが終了する(コンテキストから外れる)変数です。ほとんどの場合、変数の有効期間は関数呼び出しの期間です。これは自動変数です。、関数の開始時(または変数の宣言時)に作成され、関数の戻り時に破棄されます。変数のスコープは関数内にありますが、「範囲内」の意味はスコープが字句か動的かによって異なります。ただし、Cなどの一部の言語では、静的ローカル変数も提供されます。変数の有効期間はプログラムの有効期間全体ですが、変数は関数内のコンテキストにのみ存在します。静的ローカル変数の場合、変数はプログラムの初期化時に作成され、静的グローバル変数のようにプログラムの終了時にのみ破棄されますが、自動ローカル変数のように関数内のコンテキストにのみ存在します。

重要なのは、字句スコープでは、関数スコープを持つ変数は、関数の字句コンテキスト内でのみスコープを持ちます。関数内で別の関数が呼び出されるとコンテキストから外れ、関数が戻るとコンテキストに戻ります。呼び出された関数にはアクセス権がありません。関数を呼び出すローカル変数に、ローカル変数は、それらが宣言されている関数の本体内のコンテキストにのみ存在します。対照的に、動的スコープでは、スコープは関数の実行コンテキストに拡張されます。ローカル変数は、別の関数が呼び出されたときにコンテキスト内に留まり、定義関数が終了したときにのみコンテキスト外になります。したがって、ローカル変数は関数のコンテキスト内にあります。それらが定義され、すべての関数が呼び出されます字句スコープとネストされた関数を持つ言語では、ローカル変数はネストされた関数のコンテキストにあります。これは、これらが同じ字句コンテキスト内にあるためですが、字句的にネストされていない他の関数にはありません。囲んでいる関数のローカル変数は、入れ子関数の非ローカル変数として知られています。関数スコープは無名関数にも適用できます。

def  square n ):
    return  n  *  n

def  sum_of_squares n ):
    total  =  0  
    i  =  0 
    while  i  <=  n 
        total  + =  square i 
        i  + =  1 
    return  total

たとえば、右側のPythonコードのスニペットでは、squaresum_of_squaresの2つの関数が定義されています。squareは数値の2乗を計算します。sum_of_squaresは、数値までのすべての平方の合計を計算します。(たとえば、square(4)は4 2  =  16であり、sum_of_squares(4)は0 2  + 1 2  + 2 2  + 3 2  + 4 2  =  30です。)

これらの各関数には、関数の引数を表すnという名前の変数があります。これらの2つのn変数は、同じ名前であるにもかかわらず、完全に分離されており、関数スコープを持つローカル変数であるため、無関係です。それぞれのスコープは、独自の字句的に分離された関数であるため、重複しません。したがって、sum_of_squaresは、それ自体のnを変更せずにsquareを呼び出すことができます同様に、sum_of_squaresにはtotalおよびiという名前の変数がありますこれらの変数は、スコープが制限されているため、 totalまたはiという名前の変数に干渉しません。それは他の関数に属している可能性があります。つまり、これらの名前と無関係な名前が同一であっても、 名前が衝突するリスクはありません。

名前のマスキングは発生していません。スコープが重複していないため、常にnという名前の変数が1つだけコンテキストに含まれています。対照的に、動的スコープを持つ言語で記述される同様のフラグメントである場合、呼び出し元の関数のnは、呼び出された関数のコンテキストに残り、スコープはオーバーラップし、新しいnによってマスク(「シャドウ」)されます。呼び出された関数で。

関数がファーストクラスのオブジェクトであり、関数に対してローカルで作成してから返すことができる場合、関数のスコープは非常に複雑になります。この場合、ネストされた関数内でローカルではない変数(関数定義内のバインドされていない変数、囲んでいるコンテキスト内の変数に解決される)は、関数自体だけでなく、そのコンテキスト(変数の)としてクロージャを作成します)を返す必要があり、その後、別のコンテキストで呼び出される可能性があります。これにはコンパイラからのサポートが大幅に多く必要であり、プログラム分析が複雑になる可能性があります。

ファイルスコープ

名前バインディングのスコープはファイルであり、ファイルスコープと呼ばれます。ファイルスコープは主にC(およびC ++)に固有であり、ファイルのトップレベル(関数内ではない)で宣言された変数と関数のスコープは、ファイル全体、またはCではなく宣言から最後までです。ソースファイル、より正確にはトランスレーションユニット(内部リンク)。これは、モジュールがファイルで識別され、より最近の言語では明示的なモジュールスコープに置き換えられるモジュールスコープの形式と見なすことができます。内部コンテキストに変数と関数を追加し、それ自体がさらにincludeステートメントを呼び出す可能性のあるincludeステートメントが存在するため、ファイルの本文のコンテキストにあるものを判別するのが難しい場合があります。

上記のCコードスニペットでは、関数名sum_of_squaresにファイルスコープがあります。

モジュールスコープ

名前バインディングのスコープはモジュールであり、モジュールスコープと呼ばれます。モジュールスコープはモジュラープログラミング言語で利用できます。モジュール(さまざまなファイルにまたがる可能性があります)は、限られたインターフェイスの情報を隠したり公開したりできるため、複雑なプログラムの基本単位です。モジュールスコープはModulaファミリーの言語で開拓されたものであり、Python(Modulaの影響を受けた)は代表的な現代の例です。

C ++など、モジュールを直接サポートしていない一部のオブジェクト指向プログラミング言語では、代わりにクラス階層によって同様の構造が提供されます。クラスはプログラムの基本単位であり、クラスはプライベートメソッドを持つことができます。これは、名前解決やスコープではなく、動的ディスパッチのコンテキストで適切に理解されますが、多くの場合、類似した役割を果たします。モジュールとクラスの両方を備えたPythonのように、これらの機能の両方が利用できる場合もあり、コード編成(モジュールレベルの関数または従来のプライベートメソッドとして)はプログラマーの選択です。

グローバルスコープ

名前バインディングのスコープはプログラム全体であり、グローバルスコープと呼ばれます。グローバル変数と呼ばれるグローバルスコープを持つ変数名は、名前の衝突や意図しないマスキングの可能性に加えて、モジュール性が低いため、少なくとも一部の言語では悪い習慣と見なされることが多く、関数スコープまたはブロックスコープが望ましいと見なされます。ただし、グローバルスコープは通常、関数の名前、クラスの名前、他のデータ型の名前など、他のさまざまな種類の名前に使用されます(言語によって異なります)このような場合、名前空間などのメカニズムを使用して衝突を回避します。

字句スコープと動的スコープ

特定の関数内にのみ存在する、スコープが制限された変数名のローカル変数を使用すると、2つの同じ名前の変数間の名前の衝突のリスクを回避できます。ただし、この質問に答えるには、2つの非常に異なるアプローチがあります。関数の「内部」にあるとはどういう意味ですか?

字句スコープ(または字句スコープ;静的スコープまたは静的スコープとも呼ばれます)では、変数名のスコープが特定の関数である場合、そのスコープは関数定義のプログラムテキストです。そのテキスト内に変数名が存在し、変数の値にバインドされていますが、そのテキストの外側では、変数名は存在しません。対照的に、動的スコープ(または動的スコープ)では、変数名のスコープが特定の関数である場合、そのスコープは関数が実行されている期間です。関数の実行中、変数名は存在し、その値にバインドされていますが、関数が戻った後、変数名は存在しません。これは、if関数がfは個別に定義された関数gを呼び出し、字句スコープでは関数gはfのローカル変数にアクセスできません( gのテキストがfのテキスト内にないと仮定)が、動的スコープでは関数gアクセスできますfのローカル変数に(gfの呼び出し中に呼び出されるため)。

$ #bash言語
$ x = 1 
$ function g () {  echo  $ x  ;  x = 2  ;  } 
$ function f () {  local  x = 3  ; g ;  } 
$ f #これは1、または3を出力しますか?
3 
$ echo  $ x  #これは1、または2を出力しますか?
1

たとえば、右側のプログラムについて考えてみます。最初の行、は、グローバル変数xを作成し、それを1に初期化します2行目の、は、 xの現在の値を出力(「エコー」)し、x2に設定(前の値を上書き)する関数gを定義します。3行目は、ローカル変数x(同じ名前のグローバル変数を非表示)を作成して3に初期化し、 gを呼び出す関数fを定義します。4行目の、はfを呼び出します5行目、x=1function g() { echo $x ; x=2 ; }function f() { local x=3 ; g ; }fecho $x、 xの現在の値を出力します

それで、このプログラムは正確に何を印刷しますか?スコープルールによって異なります。このプログラムの言語が字句スコープを使用する言語である場合、gはグローバル変数xを出力および変更します(gはfの外部で定義されているため)。したがって、プログラムは1を出力してから2を出力します。対照的に、この言語が動的スコープを使用する場合、gはfのローカル変数xを出力および変更するため(gはf内から呼び出されるため)、プログラムは3を出力してから1を出力します。(たまたま、プログラムの言語はBashです、動的スコープを使用します。したがって、プログラムは3を出力してから1を出力します。字句スコープを使用するksh93で同じコードを実行した場合、結果は異なります。)

語彙スコープ

字句スコープは、名前は常にその字句コンテキストを参照します。これはプログラムテキストのプロパティであり、言語の実装によってランタイム呼び出しスタックから独立しています。このマッチングには静的プログラムテキストの分析のみが必要なため、このタイプのスコープは静的スコープとも呼ばれます。字句スコープは、 PascalModula-2AdaなどのすべてのALGOLベースの言語、およびMLHaskellなどの最新の関数型言語で標準となっています。C言語でも使用されますさまざまな種類の制限がありますが、その構文的および意味的な親戚。静的スコープを使用すると、プログラマーは、パラメーター、変数、定数、型、関数などのオブジェクト参照を単純な名前の置換として推論できます。これにより、ローカルの命名構造を分離して理解できるため、モジュラーコードとその理由を簡単に作成できます。対照的に、動的スコープは、モジュールのコードが呼び出される可能性のあるすべての可能な実行コンテキストをプログラマーに予測させます。

プログラム A ; 
var  I 整数; 
    K char ;

    手順 B ; 
    var  K 実数; 
        L 整数;

        手順 C ; 
        var  M 実数; 
        begin 
         (* scope A + B + C *)
        end ;

     (*スコープA + B *)
    終了;

 (*スコープA *)
終了

たとえば、Pascalは字句スコープです。右側のPascalプログラムフラグメントについて考えてみます。I同じ名前の別の変数によって非表示になることはないため、変数はすべてのポイントで表示されますchar変数はプロシージャでのみ表示される変数Kによって非表示になっているため、メインプログラムでのみ表示されます変数もプロシージャでのみ表示されますが、他の変数は非表示になりません。変数はプロシージャ内でのみ表示されるため、プロシージャまたはメインプログラムからはアクセスできません。また、プロシージャはプロシージャ内でのみ表示されるため、メインプログラムから呼び出すことはできません。 realKBCLBCMCBCB

Cプロシージャの外でプログラムで宣言された別のプロシージャがあった可能性がありますBプログラム内で「C」が言及されている場所は、それが指定された2つのプロシージャのどちらをC表すかを決定します。したがって、変数のスコープと正確に類似しています。

ファーストクラスの ネストされた関数を使用する言語での字句スコープの正しい実装は、各関数値が依存する変数の値のレコードを保持する必要があるため、簡単ではありません(関数とこのコンテキストのペアは呼び出されます)クロージャ実装とコンピュータアーキテクチャによっては、非常に深く字句的にネストされた関数を使用すると、変数ルックアップ がわずかに非効率になる場合があります[引用が必要]。ただし、これを軽減するためのよく知られた手法があります。[8] [9]また、独自の引数と(すぐに)ローカル変数のみを参照するネストされた関数の場合、コンパイル時にすべての相対位置を知ることができます。したがって、そのタイプの入れ子関数を使用する場合、オーバーヘッドはまったく発生しません。同じことが、ネストされた関数が使用されないプログラムの特定の部分に当てはまり、当然、ネストされた関数が使用できない言語(C言語など)で記述されたプログラムにも当てはまります。

歴史

字句スコープは、1960年代初頭に命令型言語ALGOL 60で最初に使用され、それ以来、他のほとんどの命令型言語で採用されてきました。[4]

PascalCのような言語は、 ALGOL60ALGOL68に取り入れられたアイデアの影響を受けているため、常に字句スコープを持っていました(ただし、Cには字句的にネストされた関数は含まれていませんでした)。

Perlは、後で静的スコープを追加した動的スコープを持つ言語です。

オリジナルのLispインタプリタ(1960)は動的スコープを使用していました。静的(字句)スコープに近いディープバインディングは、1962年頃にLISP 1.5で導入されました(John McCarthyの下で動作するSteve Russellによって開発されたFunargデバイスを介して)。

初期のLispはすべて、インタプリタに基づく場合、動的スコープを使用していました。1982年、Guy L. SteeleJr。とCommonLISP Groupは、Common LISPの概要[10]を公開しその時点までのLispの歴史と多様な実装の簡単なレビュー、およびCommonLispの機能のレビューを発表しました。実装が必要です。102ページで、次のように読みます。

ほとんどのLISP実装は、デフォルトでインタプリタとコンパイラが正しいプログラムに異なるセマンティクスを割り当てる可能性があるという点で、内部的に一貫性がありません。これは主に、インタープリターがすべての変数が動的にスコープされると想定しているのに対し、コンパイラーは、他の方法で強制されない限り、すべての変数がローカルであると想定しているという事実に起因します。これは利便性と効率性のために行われていますが、非常に微妙なバグにつながる可能性があります。Common LISPの定義は、インタープリターとコンパイラーに正しいプログラムに同一のセマンティクスを課すことを明示的に要求することにより、このような異常を回避します。

したがって、Common LISPの実装には、字句スコープが必要でした繰り返しますが、Common LISPの概要から:

さらに、Common LISPは次の機能を提供します(そのほとんどはMacLisp、InterLisp、またはLisp Machines Lispから借用されています):( ...)完全に字句スコープの変数。いわゆる「FUNARG問題」[11] [12]は、下向きと上向きの両方の場合で完全に解決されます。

Common LISPの概要が公開された同じ年(1982年)までに、 Schemeと呼ばれるコンパイル済みの字句スコープのLispの初期設計(Guy L. Steele Jr.による)が公開され、コンパイラの実装が試みられていました。当時、Lispの字句スコープは一般的に実装が非効率的であると恐れられていました。A History of Tで、[13] OlinShiversは次のように書いています

当時本番環境で使用されていたすべての深刻なLispは動的にスコープされていました。Rabbit [14]の論文(1978年にGuy Lewis Steele Jr.によって書かれた)を注意深く読んでいない人は誰も、語彙スコープが飛ぶとは信じていませんでした。それを読んだ少数の人々でさえ、これが本格的な本番環境で機能するだろうという信念を少し飛躍させていました。

「字句スコープ」という用語は少なくとも1967年にさかのぼりますが[15]、「字句スコープ」という用語は少なくとも1970年にさかのぼり、プロジェクトMACでLisp方言MDL(当時は「混乱」)。[16]

動的スコープ

動的スコープは、名前は実行コンテキストを参照します。現代語では珍しいことです。[4]技術用語では、これは各名前がバインディングのグローバルスタックを持っていることを意味します。名前の付いたローカル変数を導入するとx、バインディングがグローバルxスタック(空になっている可能性があります)にプッシュされ、制御フローがスコープを離れるとポップオフされます。任意のコンテキストで評価xすると、常に最上位のバインディングが生成されます。バインディングスタックは実行時にのみ存在するため、これはコンパイル時に実行できないことに注意してください。このタイプのスコープは動的スコープと呼ばれます。

一般に、特定のブロックは、その存続期間がブロックの実行時間であるバインディングを作成するために定義されます。これにより、静的スコープのいくつかの機能が動的スコーププロセスに追加されます。ただし、コードのセクションはさまざまな場所や状況から呼び出すことができるため、変数が使用される場合(または存在する場合)にどのバインディングが適用されるかを最初に決定するのは難しい場合があります。これは有益な場合があります。知識が最も少ないという原則の適用は、理由に応じてコードが回避することを示唆しています変数の値(またはその状況)の場合。ただし、変数の定義に従って値を使用するだけです。共有データのこの狭い解釈は、関数の動作をシステムの現在の状態(またはポリシー)に適応させるための非常に柔軟なシステムを提供できます。ただし、この利点は、この方法で使用されるすべての変数の注意深い文書化と、変数の動作に関する仮定の注意深い回避に依存しており、プログラムのさまざまな部分間の干渉を検出するメカニズムを提供しません。PerlCommonLispなどの一部の言語では、プログラマーが変数を定義または再定義するときに静的スコープまたは動的スコープを選択できます。動的スコープを使用する言語の例には、LogoEmacs LispLaTeXとシェル言語のbashdashPowerShell

動的スコープの実装はかなり簡単です。名前の値を見つけるために、プログラムはランタイムスタックをトラバースし、各アクティベーションレコード(各関数のスタックフレーム)で名前の値を確認できます。実際には、これは、名前と値のペアのスタックである関連付けリストを使用することで、より効率的になります。ペアは、宣言が行われるたびにこのスタックにプッシュされ、変数がコンテキストから外れるたびにポップされます。[17] 浅いバインディングは、各名前を独自の意味のスタックに関連付ける中央参照テーブルを利用する、かなり高速な代替戦略です。これにより、実行時に特定の名前を見つけるための線形検索が回避されますが、このテーブルを適切に維持するように注意する必要があります。[17]これらの戦略はどちらも、任意の1つの変数のバインディングに対する後入先出( LIFO )の順序付けを前提としていることに注意してください。実際には、すべてのバインディングはそのように順序付けられています。

さらに単純な実装は、単純なグローバル変数を使用した動的変数の表現です。ローカルバインディングは、プログラムからは見えないスタック上の匿名の場所に元の値を保存することによって実行されます。そのバインディングスコープが終了すると、元の値がこの場所から復元されます。実際、動的スコープはこの方法で発生しました。Lispの初期の実装では、ローカル変数を実装するためにこの明白な戦略を使用しました。この慣習は、GNU EmacsLispなどのまだ使用されている一部の方言で存続します。字句スコープは後でLispに導入されました。これは、中央参照テーブルが単にグローバル変数バインディングコンテキストであり、変数の現在の意味がそのグローバル値であることを除いて、上記の浅いバインディングスキームと同等です。グローバル変数の維持は複雑ではありません。

動的スコープは、スレッドローカルストレージに優れた抽象化を提供します、ただし、そのように使用された場合、グローバル変数の保存と復元に基づくことはできません。可能な実装戦略は、各変数がスレッドローカルキーを持つことです。変数にアクセスすると、スレッドローカルキーを使用してスレッドローカルメモリの場所にアクセスします(コンパイラによって生成されたコードによって、どの変数が動的で、どの変数が字句であるかがわかります)。呼び出し元のスレッドにスレッドローカルキーが存在しない場合は、グローバルロケーションが使用されます。変数がローカルにバインドされると、前の値はスタックの非表示の場所に格納されます。スレッドローカルストレージは変数のキーの下に作成され、新しい値がそこに保存されます。そのスレッド内の変数のネストされたオーバーライドは、このスレッドローカルの場所を保存して復元するだけです。最初の最も外側のオーバーライドのコンテキストが終了すると、

参照透過性を使用すると、動的スコープは現在の関数の引数スタックのみに制限され、字句スコープと一致します。

マクロ展開

現代語では、プリプロセッサでのマクロ展開は事実上の動的スコープの重要な例です。マクロ言語自体は、名前を解決せずにソースコードのみを変換しますが、展開が適切に行われるため、展開されたテキスト内の名前(特に自由変数)が解決されると、展開された場所に基づいて解決されます(緩く「呼び出された」)、動的スコープが発生しているかのように。

マクロ拡張に使用されるCプリプロセッサは、それ自体では名前解決を行わず、マクロが定義されている場所に依存しないため、事実上動的スコープを持っています。たとえば、マクロは次のとおりです。

#define ADD_A(x)x + a

渡された変数に追加するために展開されます。この名前は、マクロが「呼び出された」場所(適切に展開さaれた場所)に基づいてコンパイラーによって後で解決されます。ADD_A適切に、Cプリプロセッサは字句解析のみを行い、トークン化段階でマクロを拡張しますが、構文ツリーへの解析や名前の解決は行いません。

たとえば、次のコードではa、マクロ内の名前が(展開後に)展開サイトのローカル変数に解決されます。

#define ADD_A(x)x + a

void add_one int * x {   
  const int a = 1 ;    
  * x = ADD_A * x );  
}

void add_two int * x {   
  const int a = 2 ;    
  * x = ADD_A * x );  
}

修飾名

これまで見てきたように、スコープの主な理由の1つは、名前が別々のスコープを持つ必要があるという制限付きで、同一の名前が異なるものを参照できるようにすることで、名前の衝突を防ぐのに役立つことです。この制限が不便な場合があります。プログラム全体でさまざまなものにアクセスする必要がある場合、通常はすべてグローバルスコープの名前が必要になるため、名前の衝突を避けるためにさまざまな手法が必要になります。

これに対処するために、多くの言語はグローバル名を整理するためのメカニズムを提供します。これらのメカニズムの詳細、および使用される用語は、言語によって異なります。ただし、一般的な考え方では、名前のグループ自体に名前(プレフィックス)を付けることができ、必要に応じて、名前とプレフィックスで構成される修飾名でエンティティを参照できます。通常、このような名前には、ある意味で2セットのスコープがあります。修飾された名前が表示されるスコープ(通常はグローバルスコープ)と、修飾されていない名前(プレフィックスなし)が表示される1つ以上の狭いスコープです。良い。そして通常、これらのグループ自体をグループに編成することができます。つまり、ネストすることができます。

多くの言語がこの概念をサポートしていますが、詳細は大きく異なります。一部の言語には、C ++C#の名前空間などのメカニズムがあり、グローバル名をグループに編成できるようにするためにほぼ排他的に機能します。他の言語には、AdaのパッケージStandard MLの構造などのメカニズムがあり、これを、一部の名前をグループの他のメンバーだけに表示できるようにするという追加の目的と組み合わせています。また、オブジェクト指向言語では、クラスまたはシングルトンオブジェクトがこの目的を達成できることがよくあります(オブジェクト指向かどうかに関係なく)これが主な目的であるメカニズムを持っている)。さらに、言語はしばしばこれらのアプローチを融合します。たとえば、PerlのパッケージはC ++の名前空間にほぼ似ていますが、オプションでオブジェクト指向プログラミングのクラスを兼ねています。Javaは変数と関数をクラスに編成しますが、それらのクラスをAdaのようなパッケージに編成します。

言語別

代表的な言語のスコープルールは次のとおりです。

C

Cでは、スコープは、特に変数の場合、従来、リンケージまたは可視性として知られています。Cは、グローバルスコープ(外部リンケージと呼ばれる)、モジュールスコープまたはファイルスコープ(内部リンケージと呼ばれる)の形式、およびローカルスコープ(関数内)を持つ字句スコープの言語です。関数スコープ内では、ブロックスコープを介してさらにネストできます。ただし、標準Cはネストされた関数をサポートしていません。

変数の有効期間と可視性は、そのストレージクラスによって決定されます。Cのライフタイムには、静的(プログラム実行)、自動(ブロック実行、スタックに割り当て)、手動(ヒープに割り当て)の3種類があります。変数に対してサポートされているのは静的および自動のみであり、コンパイラによって処理されますが、手動で割り当てられたメモリは、さまざまな変数間で手動で追跡する必要があります。Cには、外部リンケージ(グローバル)、内部リンケージ(大まかにファイル)、およびブロックスコープ(関数を含む)の3つのレベルの可視性があります。ブロックスコープはネストすることができ、インクルードを使用することでさまざまなレベルの内部リンケージが可能です。Cの内部リンケージは、翻訳単位レベルでの可視性です。つまり、によって処理された後のソースファイルです。Cプリプロセッサ、特に関連するすべてのインクルードを含みます。

Cプログラムは、個別のオブジェクトファイルとしてコンパイルされ、リンカーを介して実行可能ファイルまたはライブラリにリンクされますしたがって、名前解決は、翻訳ユニット内の名前を解決するコンパイラ(より大まかに言うと「コンパイルユニット」ですが、これは適切に異なる概念です)と、翻訳ユニット間で名前を解決するリンカーに分割されます。詳細については、リンケージを参照してください。

Cでは、ブロックスコープを持つ変数は、宣言されたときにコンテキストに入り(ブロックの先頭ではなく)、ブロック内で(ネストされていない)関数が呼び出された場合はコンテキストから外れ、関数が戻ったときにコンテキストに戻ります。ブロックの終わりでコンテキストから外れます。自動ローカル変数の場合は、宣言時に割り当てられ、ブロックの最後で割り当てが解除されますが、静的ローカル変数の場合は、プログラムの初期化時に割り当てられ、プログラムの終了時に割り当てが解除されます。

次のプログラムは、ブロックスコープがブロックの途中でコンテキストに入り、ブロックが終了するとコンテキストを終了する(実際には割り当てが解除される)変数を示しています。

#include <stdio.h> 

int main void {  
  char x = 'm' ;   
  printf "%c \ n " x ); 
  {{
    printf "%c \ n " x ); 
    char x = 'b' ;   
    printf "%c \ n " x ); 
  }
  printf "%c \ n " x ); 
}

プログラムの出力:

m
m
b
m

Cには他のレベルのスコープがあります。[18]関数プロトタイプで使用される変数名には、関数プロトタイプの可視性があり、関数プロトタイプの最後に終了コンテキストがあります。名前が使用されていないため、これはコンパイルには役立ちませんが、ドキュメントには役立つ場合があります。GOTOステートメントのラベル名には関数スコープがあり、 switchステートメントのケースラベル名にはブロックスコープ(スイッチのブロック)があります。

C ++

プログラムで使用する予定のすべての変数は、コードの前のポイントで型指定子を使用して宣言されている必要があります。これは、前のコードで関数mainの本体の先頭でa、 b、および結果はint型でした。変数は、グローバルスコープまたはローカルスコープのいずれかになります。グローバル変数は、すべての関数の外部でソースコードの本体で宣言された変数ですが、ローカル変数は、関数またはブロックの本体内で宣言された変数です。

最新バージョンでは、ネストされた字句スコープ が許可されています。

スウィフト

Swiftには、C ++のスコープについて同様のルールがありますが、異なるアクセス修飾子が含まれています。

修飾子 即時スコープ ファイル モジュール/パッケージを含む 世界のその他の地域
開いた はい はい はい はい、サブクラスを許可します
公衆 はい はい はい はい、サブクラスを禁止します
内部 はい はい はい 番号
fileprivate はい はい 番号 番号
プライベート はい 番号 番号 番号

に移動

Goは、ブロックを使用して字句スコープが設定されます。[3]

Java

Javaは字句スコープです。

Javaクラスには、次の3種類の変数を含めることができます。[19]

ローカル変数
メソッドまたは特定のブロック内で定義されます。これらの変数は、それらが定義された場所および下位レベルに対してローカルです。たとえば、メソッド内のループはそのメソッドのローカル変数を使用できますが、その逆はできません。ループの変数(そのループのローカル)は、ループが終了するとすぐに破棄されます。
メンバー変数
フィールドとも呼ばれるのは、メソッドの外部で、クラス内で宣言された変数です。デフォルトでは、これらの変数は、そのクラス内のすべてのメソッドと、パッケージ内のすべてのクラスで使用できます。
パラメーター
メソッド宣言の変数です。

一般に、括弧のセットは特定のスコープを定義しますが、クラス内の最上位の変数は、定義で使用される修飾子キーワードに応じて動作が異なる場合があります。次の表は、各修飾子によって許可されるメンバーへのアクセスを示しています。[20]

修飾子 クラス パッケージ サブクラス 世界
公衆 はい はい はい はい
保護 はい はい はい 番号
(修飾子なし) はい はい 番号 番号
プライベート はい 番号 番号 番号

JavaScript

JavaScriptには単純なスコープルールがありますが[21]、変数の初期化と名前解決のルールは問題を引き起こす可能性があり、コールバックのクロージャの広範な使用は、定義されたときの関数の字句コンテキスト(名前解決に使用される)がとは大きく異なる可能性があることを意味します呼び出されたときの字句コンテキスト(名前解決には関係ありません)。JavaScriptオブジェクトにはプロパティの名前解決がありますが、これは別のトピックです。

JavaScriptには、関数レベルでネストされた字句スコープ[22]があり、グローバルコンテキストが最も外側のコンテキストです。このスコープは、変数と関数の両方に使用されます(関数型の変数ではなく、関数宣言を意味します)。[23] ECMAScriptlet 6以降、 andconstキーワードを使用したブロックスコープが標準です。ブロックスコープは、ブロック全体を関数でラップしてから実行することで作成できます。これは、即時呼び出し関数式(IIFE)パターンとして知られています。

JavaScriptのスコープは単純であり(字句、関数レベル)、関連する初期化と名前解決のルールが混乱の原因になります。まず、スコープ内にない名前への割り当ては、デフォルトで、ローカル変数ではなく、新しいグローバル変数を作成します。次に、新しいローカル変数を作成するには、varキーワードを使用する必要があります。次に、変数が関数の先頭に値とともに作成されundefined、代入式に達したときに変数にその値が割り当てられます。

Initialiserを持つ変数には、変数が作成されたときではなく、 VariableStatementが実行されたときにAssignmentExpressionの値が割り当てられます。[24]

これは変数の巻き上げとして知られています[25] —初期化ではなく宣言が関数の先頭に巻き上げられます。第三に、初期化の前に変数にアクセスするとundefined、構文エラーではなく、が生成されます。第4に、関数宣言の場合、変数の初期化とは異なり、宣言と初期化の両方が関数の先頭に上げられます。たとえば、次のコードは出力付きのダイアログを生成します未定義、ローカル変数宣言が引き上げられ、グローバル変数がシャドウイングされるため、初期化は行われないため、使用時に変数は未定義になります。

a  =  1 ; 
関数 f () {
  アラートa ); 
  var  a  =  2 ; 
} 
f ();

さらに、関数はJavaScriptのファーストクラスのオブジェクトであり、コールバックとして割り当てられるか、関数から返されることが多いため、関数が実行されるとき、名前の解決は、字句ではなく、最初に定義された場所(定義の字句コンテキスト)に依存します。呼び出されるコンテキストまたは実行コンテキスト。JavaScriptの特定の関数(最もグローバルなものから最もローカルなものまで)のネストされたスコープ、特にコールバックとして使用されるクロージャのスコープは、オブジェクトのプロトタイプチェーンと同様に 、スコープチェーンと呼ばれることがあります。

関数はファーストクラスのオブジェクトであるため、ネストされた関数を使用してJavaScriptでクロージャを作成できます。[26]囲み関数からネストされた関数を返すには、囲まれた関数のローカル変数が、返された関数の(非ローカル)字句コンテキストとして含まれ、クロージャが生成されます。例えば:

function  newCounter () { 
  //呼び出し時にインクリメントされるカウンターを返します(0から開始)
  //新しい値を返します
  var  a  =  0 ; 
  var  b  =  function () {  a ++ ;  返す ; }; bを返す; } c = newCounter (); アラートc ()+ '' + c ()); //「12」を出力します 
   

  
      

クロージャは、コールバックに使用されるため、JavaScriptで頻繁に使用されます。実際、ローカルコンテキストで関数をコールバックとしてフックしたり、関数から関数を返したりすると、関数本体にバインドされていない変数がある場合にクロージャが作成されます(現在の字句コンテキストのネストされたスコープに基づくクロージャのコンテキストを使用) 、または「スコープチェーン」); これは偶然かもしれません。パラメータに基づいてコールバックを作成する場合、パラメータはクロージャに格納する必要があります。そうしないと、囲んでいるコンテキスト内の変数を参照するクロージャが誤って作成され、変更される可能性があります。[27]

JavaScriptオブジェクトのプロパティの名前解決は、プロトタイプツリーの継承に基づいており(ツリーのルートへのパスはプロトタイプチェーンと呼ばれます)、変数や関数の名前解決とは別のものです。

Lisp

Lisp方言には、スコープに関するさまざまな規則があります。

オリジナルのLispは動的スコープを使用していました。Lispファミリーに静的(字句)スコープを導入し たのは、 ALGOLに触発されSchemeでした。

Maclispは、デフォルトでインタープリターで動的スコープを使用し、コンパイル済みコードではデフォルトでレキシカルスコープを使用しましたが、コンパイル済みコードはSPECIAL特定の変数の宣言を使用して動的バインディングにアクセスできます。[28]しかし、Maclispは、現代言語で期待されるよりも最適化として字句バインディングを扱い、現代Lispでの字句スコープに期待されるクロージャー機能は付属していませんでした。*FUNCTIONその問題のいくつかをやや不器用に回避するために、別の操作が利用可能でした。[29]

Common Lispは、 Clojureと同様に、Scheme [30]の字句スコープを採用しました

ISLISPには、通​​常の変数の字句スコープがあります。動的変数もありますが、すべての場合に明示的にマークされています。それらは、特別なフォームによって定義され、defdynamic特別なフォームによってバインドさdynamic-letれ、明示的な特別なフォームによってアクセスされる必要がありますdynamic[31]

Emacs LispのようなLispの他の方言は、デフォルトで動的スコープを使用します。Emacs Lispは、バッファごとに利用可能な字句スコープを持っています。[32]

Python

変数の場合、Pythonには関数スコープ、モジュールスコープ、およびグローバルスコープがあります。名前は、スコープ(関数、モジュール、またはグローバルスコープ)の開始時にコンテキストに入り、ネストされていない関数が呼び出されたとき、またはスコープが終了したときにコンテキストを終了します。変数の初期化の前に名前が使用されている場合、これにより実行時例外が発生します。変数が単純にアクセスされる(割り当てられない)場合、名前解決は、名前を最も狭い関連コンテキストに解決するLEGB(ローカル、囲み、グローバル、組み込み)ルールに従います。ただし、変数がに割り当てられている場合、デフォルトでは、スコープが割り当てではなく、レベル(関数、モジュール、またはグローバル)の先頭から始まる変数が宣言されます。これらのルールは両方とも、globalまたはでオーバーライドできますnonlocal(Python 3の場合)使用前の宣言。これにより、非ローカル変数がマスキングされている場合でもグローバル変数にアクセスし、グローバル変数または非ローカル変数に割り当てることができます。

簡単な例として、関数は変数をグローバルスコープに解決します。

>>> def  f ():
...     print x 
... 
>>> x  =  "global" 
>>> f ()
global

xが呼び出される前に定義されfいるため、の定義で参照後に定義されていても、エラーは発生しませんf字句的には、これは前方参照であり、Pythonで許可されています。

ここで、割り当てによって新しいローカル変数が作成されますが、グローバル変数の値は変更されません。

>>> def  f ():
...     x  =  "f" 
...     print x 
... 
>>> x  =  "global" 
>>> print x 
global 
>>> f ()
f 
> >> print x 
グローバル

関数内で変数を割り当てると、その変数は関数に対してローカルであると宣言されるため、そのスコープは関数全体になります。したがって、この割り当ての前に変数を使用すると、エラーが発生します。これは、ローカル変数のスコープが宣言から始まるCとは異なります。このコードはエラーを発生させます:

>>> def  f ():
...     print x 
...     x  =  "f" 
... 
>>> x  =  "global" 
>>> f ()
トレースバック(最後の最後の呼び出し):
  ファイル" <stdin> "1行目、<module>
  ファイル" <stdin> "2行目、f 
UnboundLocalError割り当て前に参照されるローカル変数 'x'

globalデフォルトの名前解決ルールは、 or nonlocal(Python 3の場合)キーワードでオーバーライドできます。以下のコードでは、のglobal x宣言はグローバル変数に解決されるgことを意味します。xしたがって、(すでに定義されているため)アクセスでき、割り当ては、新しいローカル変数を宣言するのではなく、グローバル変数に割り当てられます。globalで宣言は必要ないことに注意してくださいf—変数に割り当てられないため、デフォルトでグローバル変数に解決されます。

>>> def  f ():
...     print x 
... 
>>> def  g ():
...     global  x 
...     print x 
...     x  =  "g" 
... 
> >> x  =  "global" 
>>> f ()
global 
>>> g ()
global 
>>> f ()
g

global入れ子関数にも使用できます。ネストされていない関数の場合のように、グローバル変数への割り当てを許可することに加えて、これを使用して、非ローカル変数が存在する場合にグローバル変数にアクセスすることもできます。

>>> def  f ():
...     def  g ():
...         global  x 
...         print x 
...     x  =  "f" 
...     g ()
... 
>>> x  =  「グローバル」
>>> f ()
グローバル

nonlocalネストされた関数の場合、ネストされていない関数で使用するのと同様に、非ローカル変数に割り当てるための宣言もありglobalます。

>>> def  f ():
...     def  g ():
...        非ローカル x   #Python3のみ
...         x  =  "g" 
...     x  =  "f" 
...     g ()
.. ..     print x 
... 
>>> x  =  "global" 
>>> f ()
g 
>>> print x 
global

R

Rは、自由変数の値がグローバル変数のセットによって決定されるSの他の実装とは異なり、字句スコープの言語ですが、Rでは、関数が作成されたコンテキストによって決定されます。[33]parent.frame()スコープコンテキストには、プログラマーが望む場合に動的スコープのエクスペリエンスをシミュレートできる さまざまな機能(など)を使用してアクセスできます。

ブロックスコープはありません:

a  <  -1 
{ 
  a  <  -2 
}
メッセージa 
## 2

関数は、作成されたスコープにアクセスできます。

a  <  -1 
f  <- 関数() {
  メッセージa 
} 
f ()
## 1

関数内で作成または変更された変数はそこにとどまります。

a  <  -1 
f  <  -function () { 
  message a 
  a  <  -2 
  message a 
} 
f ()
## 1 
## 2 
message a 
## 1

関数内で作成または変更された変数は、包含スコープへの割り当てが明示的に要求されない限り、そこにとどまります。

a  <  -1 
f  <  -function () { 
  message a 
  a  <<-  2 
  message a 
} 
f ()
## 1 
## 2 
message a 
## 2

Rにはデフォルトで字句スコープがありますが、関数スコープは変更できます。

a  <  -1 
f  <  -function () { 
  message a 
} 
my_env  <  -new.env ()
my_env $ a  <  -2 
f ()
## 1
環境f  <-  my_env 
f ()
## 2

も参照してください

メモ

  1. ^ 「スコープ」と「コンテキスト」の意味については、定義を参照
  2. ^ 「動的スコープ」は、スコープではなくエクステント(存続期間)にため、形式的には不正確です。
  3. ^ たとえば、Python用のJinjaテンプレートエンジンは、デフォルトで字句スコープ(インポート用)と動的スコープ(インクルード用)の両方を使用し、動作をキーワードで指定できるようにします。コンテキストの動作のインポートを参照してください
  4. ^ 「名前解決」と「名前バインディング」は主に同義語です。狭義に言えば、「解決」は、高次の抽象的な構文のように、名前を意味と関連付けることなく、名前の特定の使用がどの名前を参照するかを決定し。一方、「バインディング」は、名前を実際の意味に関連付けます。実際には、これらの用語は同じ意味で使用されます。
  5. ^ 自己変更コードの場合、字句コンテキスト自体が実行時に変更される可能性があります。
  6. ^ 対照的に、*「名前バインディングのコンテキスト」、*「スコープに入る名前バインディング」、または*「スコープ外に出る名前バインディング」はすべて正しくありません。名前バインディングにはスコープがあり、プログラムの一部にはコンテキストがあります。

参考文献

  1. ^ 「アルゴリズム言語Algol60に関するレポート」、2.7。数量、種類、範囲
  2. ^ WG14 N1256 ( C99標準の2007更新バージョン)、6.2.1識別子のスコープ、2007-09-07
  3. ^ a b Goプログラミング言語仕様宣言と範囲、2013年11月13日のバージョン
  4. ^ a b c Borning A. CSE341--字句および動的スコープワシントン大学。
  5. ^ クロックフォード、ダグラス。「JavaScriptプログラミング言語のコード規約」2015年1月4日取得
  6. ^ バッカス、JW; ウェグスタイン、JH; Van Wijngaarden、A。; ウッダー、M。; バウアー、フロリダ州; グリーン、J。; カッツ、C。; マッカーシー、J。; ペルリス、AJ; Rutishauser、H。; サメルソン、K。; Vauquois、B。(1960)「アルゴリズム言語ALGOL60に関するレポート」。ACMの通信3(5):299。doi10.1145 /367236.367262S2CID278290_ 
  7. ^ 「関数-Javascript:MDN」関数内で定義された変数は、関数のスコープ内でのみ定義されているため、関数外のどこからでもアクセスできません。ただし、関数は、それが定義されているスコープ内で定義されているすべての変数と関数にアクセスできます。
  8. ^ プログラミング言語の語用論」、LeBlank-クックシンボルテーブル
  9. ^ 明示的なスコープ制御を使用して言語を実装するためのシンボルテーブルの抽象化」、LeBlank-Cook、1983年
  10. ^ ルイス・スティール、ガイ(1982年8月)。「CommonLISPの概要」。LFP '82:1982年のLISPと関数型プログラミングに関するACMシンポジウムの議事録:98–107。土井10.1145 /800068.802140ISBN 0897910826S2CID14517358 _
  11. ^ ジョエル、モーゼス(1970年6月)。「LISPにおけるFUNCTIONの機能」。MITAIメモ199MIT人工知能研究所。
  12. ^ スティール、ガイルイスジュニア; サスマン、ジェラルドジェイ(1978年5月)。「通訳の芸術;または、モジュール性複合体(パートゼロ、1および2)」。MITAIメモ453MIT人工知能研究所。
  13. ^ 震え、オーリン。「Tの歴史」ポール・グレアム2020年2月5日取得
  14. ^ スティール、ガイルイスジュニア(1978年5月)。「RABBIT:SCHEME用のコンパイラ」。MIT。hdl1721.1 / 6913 {{cite journal}}Cite journal requires |journal= (help)
  15. ^ 語彙スコープ」、コンピュータとプログラムの編成、パート3、p。18、ミシガン大学のGoogleブックスで。エンジニアリングサマーカンファレンス、1967年
  16. ^ 字句スコープ」、プロジェクトMAC進捗レポート、第8巻、p。80、 Googleブックス、1970年。
  17. ^ a b Scott 2009、3.4スコープの実装、p。143。
  18. ^ スコープ」、 Linux用XL C / C ++ V8.0、 IBM
  19. ^ 「メンバー変数の宣言(Java™チュートリアル> Java言語の学習>クラスとオブジェクト)」docs.oracle.com 2018年3月19日取得
  20. ^ 「クラスのメンバーへのアクセスの制御(Java™チュートリアル> Java言語の学習>クラスとオブジェクト)」docs.oracle.com 2018年3月19日取得
  21. ^ Javascript変数スコープについて知っておくべきことすべて」、 Saurab Parakhコーディングはクール 2010-02-08
  22. ^ 「注釈付きES5」es5.github.io 2018年3月19日取得
  23. ^ 「関数」MDN WebDocs 2018年3月19日取得
  24. ^ 12.2変数ステートメント」、注釈付きECMAScript 5.1、最終更新日:2012-05-28
  25. ^ JavaScriptのスコープと巻き上げ」、 Ben Cherry十分に良い 2010-02-08
  26. ^ Javascriptクロージャ、リチャードコーンフォード。2004年3月
  27. ^ JavaScriptのスコープとクロージャの説明」、Robert Nyman、2008年10月9日
  28. ^ ピットマン、ケント(2007年12月16日)。「改訂されたMaclispマニュアル(ピットマニュアル)、日曜日の朝刊」MACLISP.infoHyperMeta Inc.の宣言とコンパイラ、概念「変数」2018年10月20日取得バインドされる変数が特殊であると宣言されている場合、バインディングは、インタープリターが変数をバインドする方法を模倣するコードとしてコンパイルされます。
  29. ^ ピットマン、ケント(2007年12月16日)。「改訂されたMaclispマニュアル(ピットマニュアル)、日曜日の朝刊」MACLISP.infoHyperMeta Inc.エバリュエーター、特別フォーム2018年10月20日取得「 funarg問題」の解決を支援することを目的としていますが、それはいくつかの簡単な場合にのみ機能します。*FUNCTION*FUNCTION
  30. ^ ピットマン、ケント; etal。(ANSI規格X3.226-1994のウェブバージョン)(1996)。「CommonLispHyperSpec」Lispworks.comLispWorks Ltd.1..1.2歴史2018年10月20日取得MacLispは特別な変数のLisp1.5の概念を改善しました... Common Lispへの主な影響は、Lisp Machine Lisp、MacLisp、NIL、S-1 Lisp、Spice Lisp、およびSchemeでした。
  31. ^ 「プログラミング言語ISLISP、ISLISPワーキングドラフト23.0」(PDF)ISLISP.info11.1字句の原則2018年10月20日取得動的バインディングは、別のメカニズム(つまり、、、、および)によって確立およびアクセスさます defdynamicdynamic-letdynamic
  32. ^ 「字句バインディング」EmacsWiki 2018年10月20日取得Emacs 24にはオプションの字句バインディングがあり、バッファーごとに有効にできます。
  33. ^ 「RFAQ」cran.r-project.org 2018年3月19日取得