- Common LispではシンボルがデフォルトでI大文字に正規化されるため、
xとXは同一のシンボルになります。 loop for x in Xが動くのは、in Xの評価が終わった直後に反復変数xの束縛が始まるというタイミングの問題です。- ループ本体では元のリストを参照できなくなるため、ループ内でリスト自体を使いたい場合は別の変数名が必要です。
- 複数形
xsと単数xの組み合わせが、シャドウイングを避けつつ意図を明示できる実用的な命名です。
1. x と X は同じシンボル
次のコードを見てください。
(defun print-elements (X)
(loop for x in X
do (princ x)))Code language: Lisp (lisp)
X はリスト、x はその要素、という数学的な記法です。
直感的でわかりやすいですし、実際、これは正しく動きます。
でも、ちょっと気になるのが、Common Lisp はシンボルの大文字・小文字を区別しないこと。x と X は同じシンボルのはずです。
Common Lisp のリーダーはコードを読み込む際、デフォルトでシンボル名を大文字に正規化します。
REPLで確認してみましょう。
CL-USER> (eq 'x 'X)
T
CL-USER> (symbol-name 'x)
"X"Code language: Lisp (lisp)
'x と 'X は同一のシンボルです。symbol-name で内部表現を取り出すと、どちらも "X" になります。
つまり、先ほどの関数は、実質的にこう書いているのと同じです。
(defun print-elements (X)
(loop for X in X
do (princ X)))Code language: Lisp (lisp)
2. スコープとシャドウイング
loop for X in X という式は、一見おかしく見えます。
ふたつある同じ X は、どう解釈されるのでしょうか。
答えは「シャドウイング」です。
loop が for x in X を処理するとき、まず in X の部分でリストを評価し、次に反復変数 x を新しく束縛します。
この束縛がスコープ内で外側の X を隠します。
動作の順序をコメントで示すと、こうなります。
(defun print-elements (X) ; X にリストが束縛される
(loop for x ; ここから x(= X)が新しく束縛される
in X ; この X はまだ外側のリスト
do (princ x))) ; この x はループの反復変数Code language: Lisp (lisp)
in X の評価時点ではまだ外側の X が見えていて、その直後から反復変数の X が前面に出てきます。
処理系がうまいタイミングで切り替えてくれているわけです。
2.1. シャドウイングには制約がある
この仕組みを意図的に利用するなら、制約を理解しておく必要があります。
ループの本体では、元のリスト X を参照できなくなります。
(defun find-first (X)
(loop for x in X
do (when (= x 1)
(return X)))) ; ここで X を返したいCode language: Lisp (lisp)
このコードで return X の X が指しているのは、ループの反復変数です。
呼び出し時に渡したリストではありません。
実行してみると、リストではなく現在の要素が返ります。
CL-USER> (find-first '(3 1 2))
1 ; リスト (3 1 2) ではなく、要素 1 が返るCode language: Lisp (lisp)
これは、シャドウイングの当然の帰結ですが、意図した動作とは異なるわけです。
3. 命名をどう選ぶか
数列 X の項 x、という数学的な記法は、問題の構造をそのままコードに写せます。
しかし、もし、ループ内で元のリストを使いたい場合は、別の変数に退避させるか、最初から別名をつける必要があります。
Lisp では複数形を xs、単数を x と書く慣習があります。
(defun find-first (xs)
(loop for x in xs
do (when (= x 1)
(return xs)))) ; xs はシャドウされていないCode language: Lisp (lisp)
ループ内で元のリストを参照する必要があるなら、xs と x の組み合わせが安全です。
シャドウイングの問題を回避できるだけでなく、「これはリストである」という意図が名前から読み取れます。
あるいは、数学的な文脈を強調したいなら X と x、あるいは Xn も選択肢に入ります。
ただしその場合は、シャドウイングの制約を意識した上で使う必要があります。
4. 大文字・小文字を本当に区別したい場合
シンボルを縦棒 | で囲むと、エスケープされ正規化が行われません。
CL-USER> (eq '|x| '|X|)
NIL
CL-USER> (symbol-name '|x|)
"x"Code language: Lisp (lisp)
|x| と |X| は別物です。
ただし、この記法が実際に必要になる場面は多くありません。
他の言語との連携やマクロの特殊な用途など、限られたケースで使います。
通常のコードでは、命名規約で意図を明示する方向で解決できます。
5. まとめ
X と x が同じシンボルでも loop が動く理由は、シャドウイングです。
処理系がうまく機能しているように見えますが、それはあくまで評価タイミングの副産物です。
ループ内で元のリストを参照できなくなる制約を知った上で、命名を選んでください。