Common Lispのループ変数シャ
ドウイング
(大文字・小文字)

  • Common LispではシンボルがデフォルトでI大文字に正規化されるため、xXは同一のシンボルになります。
  • loop for x in Xが動くのは、in Xの評価が終わった直後に反復変数xの束縛が始まるというタイミングの問題です。
  • ループ本体では元のリストを参照できなくなるため、ループ内でリスト自体を使いたい場合は別の変数名が必要です。
  • 複数形xsと単数xの組み合わせが、シャドウイングを避けつつ意図を明示できる実用的な命名です。

関連記事

1. xX は同じシンボル

次のコードを見てください。

(defun print-elements (X)
  (loop for x in X 
        do (princ x)))Code language: Lisp (lisp)

X はリスト、x はその要素、という数学的な記法です。
直感的でわかりやすいですし、実際、これは正しく動きます。

でも、ちょっと気になるのが、Common Lisp はシンボルの大文字・小文字を区別しないこと。
xX は同じシンボルのはずです。

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 は、どう解釈されるのでしょうか。
答えは「シャドウイング」です。

loopfor 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 XX が指しているのは、ループの反復変数です。
呼び出し時に渡したリストではありません。

実行してみると、リストではなく現在の要素が返ります。

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)

ループ内で元のリストを参照する必要があるなら、xsx の組み合わせが安全です。
シャドウイングの問題を回避できるだけでなく、「これはリストである」という意図が名前から読み取れます。

あるいは、数学的な文脈を強調したいなら Xx、あるいは Xn も選択肢に入ります。
ただしその場合は、シャドウイングの制約を意識した上で使う必要があります。

4. 大文字・小文字を本当に区別したい場合

シンボルを縦棒 | で囲むと、エスケープされ正規化が行われません。

CL-USER> (eq '|x| '|X|)
NIL
CL-USER> (symbol-name '|x|)
"x"Code language: Lisp (lisp)

|x||X| は別物です。
ただし、この記法が実際に必要になる場面は多くありません。
他の言語との連携やマクロの特殊な用途など、限られたケースで使います。
通常のコードでは、命名規約で意図を明示する方向で解決できます。

5. まとめ

Xx が同じシンボルでも loop が動く理由は、シャドウイングです。
処理系がうまく機能しているように見えますが、それはあくまで評価タイミングの副産物です。
ループ内で元のリストを参照できなくなる制約を知った上で、命名を選んでください。