はじめに
「なぜ
inner()関数から外の変数が見えるの?」
その答えが、レキシカルスコープ(静的スコープ) にあります。
レキシカルスコープ(Lexical Scope) とは、
変数の有効範囲が「定義された位置」によって決まる仕組みです。
Dart や JavaScript、Kotlin、Python など多くのモダン言語が採用している
「静的スコープ(Static Scope)」とも呼ばれるルールです。
スコープとは?
まず「スコープ」とは、変数を参照できる範囲のことです。
例えば:
void main() {
int x = 10;
print(x); // ✅ OK(同じスコープ内)
{
int y = 20;
print(y); // ✅ OK
}
print(y); // ❌ Error: yはこのスコープ外
}
ここでは {} ブロックによってスコープが区切られています。
Dartではブロックスコープが基本です。
レキシカルスコープの仕組み
レキシカルスコープでは、
「関数がどこで定義されたか」 に基づいて 変数の参照範囲が決まります。
例
int x = 10;
void outer() {
int x = 20;
void inner() {
print(x);
}
inner();
}
void main() {
outer(); // 出力:20
}
どうして 20 になるの?
-
inner()関数はouter()の中で 定義 されています。 - したがって、
inner()内から見えるスコープは 定義時の外側、つまりouter()のスコープです。 - そのため、
x=20が使われ、外のx=10は無視されます。
「どこで定義されたか」が重要。
「どこから呼ばれたか」は関係ありません。
ダイナミックスコープとの比較
「レキシカルスコープ」とよく対比されるのが「ダイナミックスコープ」です。
| 種類 | スコープの決まり方 | 主な言語 |
|---|---|---|
| レキシカルスコープ(静的) | 関数が定義された場所で決まる | Dart, JavaScript, Kotlin, Python |
| ダイナミックスコープ(動的) | 関数が呼び出された場所で決まる | 古いLisp, Bashなど |
もしダイナミックスコープだったら?
int x = 10;
void outer(void Function() f) {
int x = 20;
f(); // 呼び出し位置のスコープで決まる(仮に)
}
void inner() {
print(x);
}
void main() {
outer(inner); // ダイナミックスコープなら 20(呼び出し元が基準)
}
しかし Dart は レキシカルスコープ なので、結果は 10 になります。
(inner は main のスコープで定義されているから)
レキシカルスコープとクロージャ
この仕組みを活用して、Dart では「クロージャ(closure)」が実現できます。
例:カウンター関数
Function makeCounter() {
int count = 0;
return () {
count++;
print(count);
};
}
void main() {
var counter = makeCounter();
counter(); // 1
counter(); // 2
}
何が起きている?
-
makeCounter()が終了しても、
countはreturnされた関数の中で保持され続ける。 - これは、
count変数が レキシカルスコープ によって
関数と一緒に「閉じ込められて(close over)」いるから。
これが クロージャ の本質です。
まとめ
| キーワード | 内容 |
|---|---|
| 定義位置 | スコープは「定義された場所」で決まる |
| 呼び出し位置 | 無関係(ダイナミックスコープとの違い) |
| 主な用途 | クロージャ、関数型プログラミング |
| 関連ワード | 静的スコープ、スコープチェーン、クロージャ |