public_notes/content/20240428125606 ダイナミックスコープ レキシカルスコープ.md

96 lines
No EOL
5.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 <stdio.h>
// 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 <stdio.h>
// 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ではダイナミックスコープになってしまったらしい。じゃあその頃、他の言語はどうだったんだ、とか、別途気になる。
レキシカルスコープがいかに重要なのかとか、どうやったらそれが実現できるのかをレキシカルスコープな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)