# 20240428125606 ダイナミックスコープ レキシカルスコープ #software #word ## とは? 以下、C/C++やjavascriptのような、グローバル変数や、関数の中で変数を定義できて、その2つは変な区別が無い言語を想定している。たとえばpythonだと、`global`宣言をしないと関数内でグローバル変数が参照できない・・・という区別があるし、common lispでは `defvar` や`defparameter`を使わないとグローバル変数が宣言できない・・・という区別がある。 cf. [[20240428122916 setf setq defvar defparameter の違い|setf setq defvar defparameter の違い]] 。こういう言語は、それなりに理由があってそのような区別をしているのだが、話がややこしくなるので一旦忘れる。 プログラムを動かした時、ある場所で、 - どのような変数が定義されているか - 定義されているとしたら、その値はどうなっているか を判断するにはどうしたらよいか?について、大きくわけると2つの方法がある。 1. 有効な、いちばん内側で定義されている値を使う。 2. 同じ変数名の宣言、定義、変更などが来たら、実行時、最後にそうなった値を使う。関数に入ろうが、出ようが関係ない。 現代(2024)のプログラミング言語では、1.が当然のように使われているため、1. 以外の方法がある可能性について、あまり考えたりすることが無い。(なんでこのような違いができたのか?の理屈はネットの各地で説明されていると思うが、その雰囲気とかは自分で関数呼び出しができるインタプリタとかを書いてみるのがよいかも。何かのプログラミング言語のユーザー的な視点だと、このような違いがあるのは結構な違和感) たとえば、以下のCプログラムで、画面に何が表示されるかを考えてみる。 ```c #include // global int a = 0; void f2() { printf("%d\n", a); } void f1() { int a = 1; f2(); } int main(void) { f1(); return 0; } ``` 画面に表示されるのは`0`になる。`f1()`の中で、 `int a`を再度定義しているが、それは `f1()`内部のみで有効であり、呼びだされた関数`f2()`では有効ではない。これがレキシカルスコープ。 ここで、`f1()`で定義した`int a`を`f2()`でも、この書きかただと、`f1()`を通過しているゆえ、参照できてしまうのが、ダイナミックスコープ。 その場合、画面には`1`が表示される(実際に試すことはできないけど)。 レキシカルスコープの言語では、変数名とその参照先は一意に決まる。実行された状況などに左右されない。ここでいう一意というのは、「同じ名前の変数が色々定義されてた場合、そのうちのどれ?」というのが(つまり、環境フレームが)一意という意味であって、その中身は実行された状況に左右されない、というわけではない。 ダイナミックスコープの言語では、変数の値がどうなっているかは、実行された状況に左右される(だからダイナミックスコープという)。 例えば、下記を実行したら、ダイナミックスコープの場合は`2`が表示される。実際は0だけど。 ```c #include // global int a = 0; void f3() { int a = 2; // aを2に設定 } void f2() { printf("%d\n", a); // ダイナミックスコープなら、2が表示される } void f1() { int a = 1; f3(); f2(); } int main(void) { f1(); return 0; } ``` ## 経緯とかお気持ちとか そもそもはレキシカルスコープにしたかったが、初期のLISPではダイナミックスコープになってしまったらしい。じゃあその頃、他の言語はどうだったんだ、とか、別途気になる。 レキシカルスコープがいかに重要なのか?とか、どうやったらそれが実現できるのか?をレキシカルスコープなschemeを作った人が書いたSICPが詳細に説明しているのは、そういう理由からかもしれない(根拠なし)。cf. [評価の環境モデル(SICP)](https://sicp.iijlab.net/fulltext/x320.html) ちなみにレキシカルスコープのインタプリタを実装しようとすると、関数呼び出しのとき、別の関数を呼ぶ場合、呼び出し元で定義したローカル変数のかたまり(フレーム)を一旦無効にしてから関数を呼び出す必要があり、関数から戻って来た時にフレームを再度有効にする必要がある。この用途のため、フレーム保存しておく必要があるが、再帰呼び出しなどの時、(末尾再帰でないなら)毎回そのフレームを保存しておく必要があり、メモリ消費量が多くなる。というような、「ダイナミックスコープになった経緯」みたいな話をネットでチョイチョイ見るが、単純にフレームの保存をしていないという、現代の視点から見るとバグっぽい挙動に見えてしまう。 ### Refs. - [変数束縛はLispのもう一つの本質である.](http://blog.livedoor.jp/s-koide/archives/1872474.html?ref=head_btn_prev&id=3054770) - [変数束縛はLispのもう一つの本質である(続き)](http://blog.livedoor.jp/s-koide/archives/1873931.html) ### 参考にしてはいけない - レキシカルスコープの説明しかない [クロージャ - JavaScript | MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Closures) - とてもわかりにくい [動的スコープ - Wikipedia](https://ja.wikipedia.org/wiki/%E5%8B%95%E7%9A%84%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%97)