動的スコープが役立つのはいつですか?


9

動的スコープを使用すると、呼び出し先は呼び出し元の変数にアクセスできます。疑似Cコード:

void foo()
{
    print(x);
}

void bar()
{
    int x = 42;
    foo();
}

動的スコープをサポートする言語でプログラミングしたことがないので、動的スコープの実際のユースケースはどうなるのだろうと思います。


インタプリタを他の特定の言語で実装すると、実装が簡単になるのではないでしょうか。
Alf P. Steinbach

2
それはかつてそれを使用したほとんどの言語(例えばLisp)がもはや使用しないほど十分に使用されていません。明白な例の1つとして、初期のLisp実装のほとんどは動的スコープを使用していましたが、現在ではすべての主要なバリアント(CL、Schemeなど)が字句スコープを使用しています。
ジェリーコフィン2011

1
@JerryCoffin:注目すべき例外には、PerlとEmacs Lispがあります。どちらも元々動的スコープを使用していましたが、現在(Perl 5、Emacs 24)は動的スコープと字句スコープの両方をサポートしています。選べるのは嬉しい。
Jon Purdy、2015

@JerryCoffinこれはコード化された例と完全には一致しませんが、質問を正しく理解していれば、JavaScriptは動的スコープを広く利用します。それでも、言語の短所を補うだけではない、それが提供する一般的な利点を考えようとしています。
Adrian、

@JerryCoffin Common Lispで積極的に使用されている動的スコープがまだいくつかありますが、そのほとんどは読み取りと印刷の動的制御です。
Vatine

回答:


15

動的スコープの非常に便利なアプリケーションは、コールスタック内のすべての関数に新しいパラメーターを明示的に追加する必要なく、コンテキストパラメーター渡すためのものです。

たとえば、Clojureはbindingによる動的スコープをサポートしています。これを使用して、一時的*out*に印刷用の値を再割り当てできます。再バインドする*out*と、バインディングの動的スコープ内でprintを呼び出すたびに、新しい出力ストリームに出力されます。たとえば、すべての印刷出力をある種のデバッグログにリダイレクトする場合に非常に役立ちます。

例:以下のコードでは、do-stuff関数は標準出力ではなくデバッグ出力に出力しますが、これを有効にするために出力パラメーターをdo-stuffに追加する必要がないことに注意してください。

(defn do-stuff [] 
  (do-other-stuff)
  (print "stuff done!"))

(binding [*out* my-debug-output-writer]
  (do-stuff))

Clojureのバインディングもスレッドローカルであるため、この機能を同時に使用しても問題はありません。これにより、同じ目的でグローバル変数を(ab)使用するよりもバインディングがかなり安全になります。


2

(免責事項:私は動的スコープ言語でプログラミングしたことがありません)

スコーピングは実装がはるかに簡単で、潜在的に高速です。動的スコープでは、1つのシンボルテーブルのみが必要です(現在利用可能な変数)。このシンボルテーブルからすべてを読み取ります。

Pythonで同じ関数を想像してみてください。

def bar():
    x = 42;
    foo(42)

def foo(x):
    print x

barを呼び出すとき、xをシンボルテーブルに入れます。fooを呼び出すと、barに現在使用されているシンボルテーブルを取得してスタックにプッシュします。次に、xが渡されたfooを呼び出します(関数の呼び出し時に新しいシンボルテーブルに配置されている可能性があります)。関数を終了した後、新しいスコープを破棄して古いスコープを復元する必要があります。

動的スコープでは、これは必要ありません。シンボルテーブルに対して何も実行する必要がないため、関数が終了したときに戻る必要がある命令を知るだけで済みます。


(素朴な)インタプリタでのみ「潜在的に高速」。コンパイラーは、動的スコープよりも字句スコープの方がはるかに優れています。また、「動的スコープの場合、これは必要ありません...」は間違っています。動的スコープの場合、変数のスコープが終了すると(たとえば、関数が戻る)、シンボルテーブルを更新して以前の値を復元する必要があります。実際、コードは通常、変数の現在値の可変フィールドを持つ「シンボルオブジェクト」を直接参照すると思います。毎回テーブルルックアップを実行するよりもはるかに高速です。しかし、それでも、更新作業はただ消えるだけではありません。
ライアンカルペッパー2011

ああ、私は動的スコープが関数が呼び出される前の値を復元することに気づいていませんでした。それらはすべて同じ値を参照していると思いました。
jsternberg 2011

2
はい、まだネストがあります。それ以外の場合は、すべての変数をグローバルにして、単純な代入を行うのと同じです。
ライアンカルペッパー2011

2

ほとんどの言語での例外処理は動的スコープを利用しています。例外が発生すると、制御は(動的)アクティベーションスタック上の最も近いハンドラに戻されます。


反対投票の理由についてコメントしてください。ありがとう!
Eyvind

これは興味深い見方です。またreturn、ほとんどの言語のステートメントは、スタック上の呼び出し元に制御を返すため、動的スコープを使用しているとも言えますか?
ruakh 2017年

1

これが完全に一致するかどうかは100%わかりませんが、少なくとも一般的な意味では、スコープルールの違反または変更に使用できる場所を示すのに十分近づいていると思います。

Ruby言語にはテンプレートクラスERBが付属しています。これは、たとえばRailsでHTMLファイルを生成するために使用されます。使用すると、次のようになります。

require 'erb'

x = 42
template = ERB.new <<-EOF
  The value of x is: <%= x %>
EOF
puts template.result(binding)

bindingそれはそれらにアクセスして、テンプレートを埋めるためにそれらを使用することができますので手は、ERBのメソッド呼び出しにローカル変数にアクセスできます。(EOF間のコードは文字列であり、<%=%>の間の部分はERBによってRubyコードとして評価され、関数のように独自のスコープを宣言します)

Railsの例はこれをさらによく示しています。記事のコントローラーでは、次のようなものが見つかります。

def index
  @articles = Article.all

  respond_to do |format|
    format.html
    format.xml  { render :xml => @posts }
  end
end

index.html.erbファイルは、次の@articlesようなローカル変数を使用できます(この場合、ERBオブジェクトの作成とバインディングはRailsフレームワークによって処理されるため、ここには表示されません)。

<ul>
<% @articles.each do |article| %>
  <li><%= article.name</li>
<% end %>
</ul>

したがって、バインディング変数を使用することにより、Rubyは異なるコンテキストで1つの同じテンプレートコードを実行できます。

ERBクラスは使用例の1つにすぎません。Rubyでは一般に、Kernel#bindingを使用して、変数とメソッドのバインディングで実際の実行状態を取得できます。これは、異なるコンテキストでメソッドを評価したい場合や、後で使用するためにコンテキストを保持したい場合に非常に役立ちます。


1

動的スコープの使用例は、グローバル変数の場合と同じIHMOです。動的スコープにより、変数のより制御された更新を可能にするグローバル変数の問題の一部が回避されます。

私が考えることができるいくつかのユースケース:

  • ロギング:字句コンテキストからではなく、ランタイムコンテキストによってログをグループ化する方が理にかなっています。ロガーを要求ハンドラで動的にバインドして、そこから呼び出されるすべての関数がログエントリで同じ「requestID」を共有するようにすることができます。
  • 出力リダイレクト
  • リソースアロケータ:特定のアクション/リクエストに割り当てられたリソースを追跡します。
  • 例外処理
  • Emacsが特定のコンテキストでキーマッピングを変更するなど、一般的なコンテキスト動作

もちろん、動的スコープは「絶対に必要」ではありませんが、呼び出しチェーンに沿ってトランプデータを渡さなければならない、またはスマートグローバルプロキシとコンテキストマネージャを実装する必要があるという負担から解放されます。

しかし、繰り返しになりますが、動的スコープは、しばしばグローバルとして扱われる種類の要素(ログ、出力、リソースの割り当て/管理など)を扱うときに便利です。


-2

ほとんどありません。たとえば、同じ変数を2回使用しないでください。

void foo() {
    print_int(x);
}
void bar() {
    print_string(x);
}

では、同じ関数からfooとbarの両方をどのように呼び出すのでしょうか。

これは、単にグローバル変数を使用することと実質的に同じであり、同じ理由で悪いことです。


2
x = 42; foo(); x = '42'; bar();
Luc Danton
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.