3. 名前と束縛

3.1. 名前について

プログラムの構成要素を指し示すためには、名前(name)をつける必要がある。識別子(identifier)とも言う。

  • 名前の構文規則は言語によって違うが、だいたい英数字といくつかの記号から成る列であることが多い。

  • 長さに制限があることが多い。言語によって決まっている場合と、処理系によって決まっている場合がある。

  • 英字の大文字と小文字を同一視する言語としない言語がある。

文法によって決められた役割を持つ語をキーワード(keyword)と言う。ユーザが名前として使えないキーワードを予約語(reserved word)と言う。さらに、キーワードではないが標準的に特定の意味で使われる名前(e.g. 入出力ライブラリなど)があることも多い。

FORTRANの型宣言

FORTRAN には予約語がないので、次のプログラムは文法的に正しい。

INTEGER REAL
REAL INTEGER
REAL = 3
INTEGER = 1.2

JavaScriptのundefined

undefined は予約語ではない(代入はできないようになっている)。自分で新しく宣言することは可能。

{
  const undefined = 1
  console.log(undefined)
}

3.2. 名前空間

たくさん名前が必要な場合、うっかり同じ名前になってしまわないよう長い名前を付けなければならないが、あまり長くなると読み書きが大変になる。そこで、ファイルの集合にディレクトリ名(あるいはフォルダ名)を付けるのと同じように、名前の集合に対して名前を付け、階層的に名前を指定できるようにしたものを名前空間(namespace)と呼ぶ。

C++の名前空間

C++ では :: で名前を連結する。

#include <iostream>

main() {
  std::cout << "Hello, world!\n";
}

using namespace で名前空間の省略時解釈を指定し、短い名前を使える。

#include <iostream>
using namespace std;

main() {
  cout << "Hello, world!\n";
}

Pythonのインポート

モジュールをインポートすると、階層的な名前空間になる。

import foo
x = foo.bar()

特定の関数などを指定してインポートすると、その名前が現在の名前空間で定義される。

from foo import bar
x = bar()

3.3. 名前の束縛

3.3.1. 束縛とは

名前は構文的なものなので、それが指し示す意味的な実体と結び付ける必要がある。

  • 名前の出現を実体に結び付けることを束縛(binding)と言う。

    • 実行前(普通はコンパイル時)に分かってしまう束縛を静的(static)束縛と言う。

    • 実行してみないと分からない束縛を動的(dynamic)束縛と言う。

  • プログラムの実行のある時点で、使用されている束縛全体の集合を環境(environment)と言う。

注釈

静的束縛は、同じ名前が常に同じ実体に束縛されるという意味ではない。同じ名前の異なる出現は、異なる実体に束縛されるかもしれない。

TinyBASIC

小規模なBASIC処理系では、変数は AZ の26個だけであった。この場合は常に同じ束縛が使われている。

Javaの変数宣言

Javaにおいて、 int i; という変数宣言は、 i という名前を整数型の変数に束縛することを意味している。

課題5

CやJavaのようにあらかじめ変数を宣言する必要がある言語と、PerlやRubyのように必要がない言語について、それぞれの利点・欠点は何か?

【解答例】

3.3.2. 無名と別名

束縛は一対一の関係であるとは限らない。

  • 実体があって、名前が束縛されていないことを無名(anonymous)と呼ぶ(e.g. 無名変数、無名関数)。

  • 一つの実体に複数の名前が束縛されている時、同じ実体を指している名前はお互いに別名(alias)であると言う。

JavaScriptの無名関数

function(x) { return x+1; }

Lispの無名関数

(lambda (x) (+ x 1))

3.4. スコープ

束縛は変数宣言や関数宣言によって決定されるが、その束縛が使用される(宣言した実体が「見える」)範囲は、構文的な単位(ブロックや関数)によって決めることが多い。この範囲のことをスコープ(scope)と言う。

3.4.1. 静的スコープ(static scope)

Algol系の言語では、ブロックや関数の包含関係に関して、最も内側でその名前が宣言されているところで束縛が決まる。構文的な要素だけで決まるので、レキシカル・スコープ(lexical scope)とも言う。

procedureの重なり(静的)

procedure main と、その中に含まれる procedure sub2 で同じ名前の変数 x を宣言する。 procedure sub1 の中に出現する x は、 procedure main で宣言された変数に束縛される。

procedure main;
  var x : integer;    { (イ) sub2 を除く main がスコープ }

  procedure sub1;
    begin
      ... x ...       { (イ)を参照 }
    end;

  procedure sub2;
    var x : integer;  { (ロ) sub2 がスコープ }
    begin
      ... x ...       { (ロ)を参照 }
    end;

  { mainの本体 }
  begin
    ... x ...         { (イ)を参照 }
  end;

3.4.2. 動的スコープ(dynamic scope)

昔のLispでは、関数の呼び出し履歴で最後にその名前が宣言されていたところで束縛が決まる。実際に呼び出されないと束縛が決まらないので動的スコープと呼ぶ。

procedureの重なり(動的)

procedure sub1 の中に出現する x は、 procedure main から直接呼び出された時と、 procedure sub2 を経由して呼び出された時で違う変数に束縛される。

procedure main;
  var x : integer;  { (イ) }

  procedure sub1;
    begin
      ... x ...     { (ニ)から呼び出されたときは(イ)を参照
                      (ホ)→(ハ)と呼び出されたときは(ロ)を参照 }
    end;

  procedure sub2;
    var x : integer;  { (ロ) }
    begin
      sub1          { (ハ) }
    end;

  { mainの本体 }
  begin
    sub1;           { (ニ) }
    sub2            { (ホ) }
  end;

課題6

次のPascal風プログラムで、静的スコープと動的スコープを使用したときに、 sub1 でそれぞれ何が出力されるか答えよ。

procedure main;
  var x : integer;

  procedure sub1;
    begin
      writeln(x)  { x の値を出力 }
    end;

  procedure sub2;
    var x : integer;
    begin
      x := 10;
      sub1
    end;

  begin
    x := 5;
    sub2
  end.

【解答例】