12.3 ローカル変数

グローバル変数は新しい値で明示的に置き換えるまで値が持続します。変数にローカル値(local value) — Lispプログラム内の特定の部分で効果をもつ — を与えると便利なときがあります。変数がローカル値をもつとき、わたしたちは変数がその値にローカルにバインド(locally bound)されていると言い、その変数をローカル変数(local variable)と呼びます。

たとえば関数が呼び出されるとき、関数の引数となる変数はローカル値(その関数の呼び出しにおいて実際の引数に与えられた値)を受け取ります。これらのローカルバインディングは関数のbody内で効果をもちます。他にもたとえばスペシャルフォームletは特定の変数にたいして明示的にローカルなバインディングを確立して、これはletフォームのbody内だけで効果を持ちます。

これにたいしてグローバルなバインディング(global binding)とは、(概念的には)グローバルな値が保持される場所です。

ローカルバインディングを確立すると、その変数の以前の値は他の場所に保存されます(または失われる)。わたしたちはこれを、以前の値がシャドー(shadowed)されたと言います。シャドーはグローバル変数とローカル変数の両方で発生し得ます。ローカルバインディングが効果を持つときには、ローカル変数にsetqを使用することにより、指定した値をローカルバインディングに格納します。ローカルバインディングが効果を持たなくなったとき、以前にシャドーされた値が復元されます(または失われる)。

変数は同時に複数のローカルバインディングを持つことができます(たとえばその変数をバインドするネストされたlet)。カレントバインディング(current binding)とは、実際に効果を持つローカルバインディングのことです。カレントバインディングは、その変数の評価によりリターンされる値を決定し、setqにより影響を受けるバインディングです。

ほとんどの用途において、最内(innermost)のローカルバインディングとローカルバインディングをもたないグローバルバインディングを、カレントバインディングと考えることができます。より正確に言うと、スコープルール(scoping rule)と呼ばれるルールは、プログラム内でローカルバインディングが効果を持つ任意の与えられた場所を決定します。Emacs Lispのスコープルールはダイナミックスコープ(dynamic scoping)と呼ばれ、これは単に実行中のプログラム内の与えられた位置でのカレントバインディングを示しており、その変数がまだ存在すれば、その変数にたいしてもっとも最近作成されたバインディングです。ダイナミックスコープについての詳細、およびその代替であるレキシカルスコープ(lexical scoping)と呼ばれるスコープルールについては、変数のバインディングのスコーピングルールを参照してください。Emacsの最近の動向として、レキシカルバインディングをデフォルトにするという最終ゴールに向けて、レキシカルバインディングがますます多くの場所で使用される方向に進んでいます。特にEmacs Lispのソースファイルすべてと*scratch*バッファーではレキシカルなスコープが使用されています。

スペシャルフォームletlet*は、ローカルバインディングを作成するために存在します:

Special Form: let (bindings…) forms…

このスペシャルフォームはbindingsにより指定される特定の変数セットにたいするローカルバインディングをセットアップしてから、formsのすべてをテキスト順に評価する。これはforms内の最後のフォームの値をリターンする。letがセットアップしたローカルバインディングはformsのbody内でのみ効果をもつ。

bindingsの各バインディングは2つの形式のいずれかである。(i) シンボルなら、そのシンボルはnilにローカルにバインドされる。(ii) フォーム(symbol value-form)のリストなら、symbolvalue-formを評価した結果へローカルにバインドされる。value-formが省略されたらnilが使用される。

bindings内のすべてのvalue-formは、シンボルがそれらにバインドされるに、記述された順番に評価される。以下の例ではzyの新しい値(つまり1)にではなく、古い値(つまり2)にバインドされる。

(setq y 2)
     ⇒ 2

(let ((y 1)
      (z y))
  (list y z))
     ⇒ (1 2)

その一方でbindingsの順序は指定されない。以下の例では1か2のどちらかがプリントされる。

(let ((x 1)
      (x 2))
  (print x))

したがって単一のletフォーム内で変数を複数回バインディングするのは避けること。

Special Form: let* (bindings…) forms…

このスペシャルフォームはletと似ているが、次の変数値にたいするローカル値を計算する前に、ローカル値を計算してそれを変数にバインドする。したがてbindings内の式は、このlet*フォーム内の前のシンボルのバインドを参照できる。以下の例を上記letの例と比較されたい。

(setq y 2)
     ⇒ 2

(let* ((y 1)
       (z y))    ; yの値に今計算されたばかりの値を使用する
  (list y z))
     ⇒ (1 1)

上記例におけるxylet*バインディングは、基本的には以下のようなネストされたletを使うのと同じ:

(let ((y 1))
  (let ((z y))
    (list y z)))
Special Form: letrec (bindings…) forms…

このスペシャルフォームはlet*と同様だが、ローカル値を計算する前にすべての変数をバインドする。値はその後にバインドされた変数にローカルに割り当てられる。これはレキシカルバインディングが効力をもち、let*の使用しても有効にならないバインディングを参照するクロージャを作成したい場合のみ有用。

たとえば以下は一度実行後にフックから自身を削除するクロージャ:

(letrec ((hookfun (lambda ()
                    (message "Run once")
                    (remove-hook 'post-command-hook hookfun))))
  (add-hook 'post-command-hook hookfun))
Special Form: dlet (bindings…) forms…

このスペシャルフォームはletと似ているが、すべての変数をダイナミックにバインドする。これが役に立つことは稀だろう — 通常ならノーマル変数はレキシカルに、スペシャル変数(defvarで定義された変数)はダイナミックにバインドしたいと望むだろうし、これは正にletが行うことだからだ。

特定の変数がダイナミックにバインド(ダイナミックバインディングを参照)されていることを想定している古いコードとインターフェイスをとる際、それらの変数をdefvarとするのは非現実的な場合にdletは有用かもしれない。dletはこれらの変数を一時的にスペシャルとしてバインドしてフォームを実行、それから再び変数を非スペシャルにする。

Special Form: named-let name bindings &rest body

このスペシャルフォームはScheme言語からインスパイアされたロープ構文である。これはletのようにbindings内の変数をバインドしてからbodyを評価する。ただしnamed-letは正規の引数がbindings内の変数で本体がbodyであるようなローカル関数にたいしてnameのバインドも行う。これによりnameを呼び出してbodyが自身を再帰的に呼び出すことができる。再帰呼び出しにおいては、バインドされる変数の新たな値としてnameに渡された引数が使用される。

数値にリストを加算するループの例:

(named-let sum ((numbers '(1 2 3 4))
                (running-sum 0))
  (if numbers
      (sum (cdr numbers) (+ running-sum (car numbers)))
    running-sum))
⇒ 10

body末尾位置(tail positions)におけるnameへの再帰呼び出しは、末尾呼び出し(tail calls)としての最適化が保証される。これは再帰の実行深さに関わらず追加のスタック空間を消費しないことを意味する。このような再帰呼び出しでは、変数にたいして新たな値でループ先頭に効果的にジャンプを行う。

一番最後に行うことが関数呼び出しならそれは末尾位置にあり、したがってその呼び出しのリターン値は上記sumにたいする再帰呼び出しと同じようにbodyの値となる。

named-letが使用できるのはレキシカルバインディングが有効な場合のみ。See レキシカルバインディングを参照のこと。

以下はローカルバインディングを作成する他の機能のリストです:

変数はバッファーローカルなバインディングを持つこともできます(バッファーローカル変数を参照)。数は多くありませんが、端末ローカル(terminal-local)なバインディングをもつ変数もあります(複数の端末を参照)。この種のバインディングは、通常のローカルバインディングのように機能することもありますが、これらはEmacs内のどこにいるかに依存してローカルになります。


This page has generated for branch:work/emacs-30_69b16e5c63840479270d32f58daea923fe725b90, commit:5e3f74b56ff47b5bcef2526c70f53f749bbd45f6 to check Japanese translation.