Emacs Lispの汎変数

Emacs Lisp には「汎変数」（general variable) という機能があります。従来はcl パッケージに含まれれていましたが、Emacs24.3 から 標準ライブラリへ移行しました。


# 汎変数とは何か

汎変数は、関数を変数的に扱える枠組みです。従来、変数 `a` に 10 を代入する場合は、

```el
(setq x 10)
```

としていました。汎変数は、変数を関数に拡張する枠組みです。関数 `elt`を例にとりますと、 `a` が `(1 2 3)` である場合、 `a` の２番目の要素は、

```el
(elt a 1)
```

で取り出すことができます。この `a` の２番目の要素に 10 を設定したい場合、 `setq` の代わりに `setf` で以下のように指定します。

```el
(setf (elt a 1) 10)
```

これで `a` の値は `(1 10 3)` になります。

この `setf` は `setq` の拡張と見ることができ、置き換えることができます。`setq` では、 `x` が変数でしたが、 `setf` では、 `(elt a 1)` 全体が変数とみなされます。


# マクロと特別形式

ところで、Emacs Lisp には、関数に類する機能として、マクロと特別形式(special form) があります。関数は実行時に評価されますが、マクロはコンパイル時に評価されて、式を出力します。また、実行時にはさらに式が評価されて結果を出力します。特別形式は、引数が評価されない場合があります。

たとえば `setq` は特別形式で、奇数番目の引数は評価されません。（詳細はEmacs Lisp リファレンス・マニュアル 9.2.7 参照）

一方、 `setf` はマクロです。式がどのようにマクロ展開されているかを確認するには、 `macroexpand` を使います。上の例の `setf` はマクロ展開すると以下のようになります。

```el
(macroexpand '(setf (elt a 1) 10)) ↩

(let* ((v a)) (if (listp v) (setcar (nthcdr 1 v) 10) (aset v 1 10)))
```

（ `elt` の引数の `a` が、一旦、 `let*` 文の冒頭で新変数 `v` に退避され、それを評価する式が `let*` 文の中で展開されています。このようになっている理由は後で説明します。）


# 汎変数を扱うマクロとその例

汎変数はマクロであり、コンパイル時に適切な形に展開されます。

以下に汎変数マクロの一覧を示します。 `push`, `pop` を除き、マクロの末尾に `f` が付きます。

| マクロ名 | 変数型 | 説明                                       |
|:---------|:-------|:-------------------------------------------|
| setf     | 任意   | setq と同様、汎変数に値を設定する          |
| incf     | 数値   | 汎変数の値を１（または任意数）増やす       |
| decf     | 数値   | 汎変数の値を１（または任意数）減らす       |
| remf     | plist  | 汎変数plist からシンボルの値を除去する     |
| shiftf   | 任意   | 汎変数群の値を１つずつずらす               |
| rotatef  | 任意   | 汎変数群の値をローテートする               |
| callf    | 任意   | 汎変数の値に関数を適用する                 |
| callf2   | 任意   | 汎変数の値に関数を適用する（関数の第二引数）|
| letf     | 任意   | 一時的に汎変数の値を変更して評価する       |
| letf\*   | 任意   | 一時的に汎変数の値を変更して評価する       |
| getf     | plist  | 汎変数plistからシンボルの値を取得する      |
| push     | リスト | 汎変数リストの先頭に要素を追加             |
| pushnew  | リスト | 汎変数リストの先頭に新要素を追加           |
| pop      | リスト | 汎変数リストの先頭から要素を取得           |

以下に `elt` と配列を使った例を示します。多くは、汎変数を使わないとより複雑になります。

```el
;; シンボルに代入する際は、setqとsetf は同じです。
(setf a [1 "hoge" (1 2 3 4 5)]) ↩
[1 "hoge" (1 2 3 4 5)]

;; 配列の先頭の要素を１増加させます。
(incf (elt a 0)) ↩
a ↩
[2 "hoge" (1 2 3 4 5)]

;; 配列の２番目の文字列の末尾に "page" を追加します。
(callf concat (elt a 1) "page") ↩
a ↩
[2 "hogepage" (1 2 3 4 5)]

;; 配列の３番目のリストから偶数を削除します
(callf2 remove-if 'evenp (elt a 2)) ↩
a ↩
[2 "hogepage" (1 3 5)]

;; 配列の３番目のリストの先頭を100にします。
(setf (car (elt a 2)) 100) ↩
a ↩
[2 "hogepage" (100 3 5)]

;; 配列の最初と最後を入れ替えます。
(rotatef (elt a 0) (elt a 2)) ↩
a ↩
[(100 3 5) "hogepage" 2]

;; 一時的に、配列の３つめを999にします。
(letf (((elt a 2) 999))
  (format "%s" a)) ↩
"[(100 3 5) hogepage 999]"
a ↩
[(100 3 5) "hogepage" 2]

;; "hogepage" の最初の "ge" を "moge" に置き換えます。
(setf (substring (elt a 1) 2 4) "moge") ↩
a ↩
[(100 3 5) "homogepage" 2]

;; 配列の１つめのリストの先頭に10を追加します。
(push 10 (elt a 0)) ↩
a ↩
[(10 100 3 5) "homogepage" 2]

```

汎変数マクロ `callf`, `callf2` はもっとも基本となるマクロです。たとえば `(setq a (concat a ".x"))` と `(callf concat a ".x")` が同等、または `(setq a (concat "x." a))` と `(callf2 concat "x." a)` が同等です。


# 汎変数に対応する関数

上記の例に示した `elt` や `substring` 等を含め、リストや文字列などにアクセスする多くの標準関数が、汎変数に対応しており、上述のマクロと組み合わせることで、可読性の高いコードを記述できます。

また、マクロ `defstruct` で定義される構造体メンバや、関数のコンパイラマクロ (`compiler-macro`) も汎変数として適用できます（エイリアスや置換含む）。

汎変数に対応している関数は、関数名シンボルの `gv-expander` プロパティにラムダ関数が設定されています。以下に例を示します。

（プロパティの詳細については、ELisp リファレンス・マニュアルの 8.4節を参照ください。）

```el
(pp (function-get 'elt 'gv-expander t)) ↩

(closure
 (t)
 (do &rest args)
 (gv--defsetter 'elt
                (lambda (store seq n)
                  `(if (listp ,seq)
                       (setcar
                        (nthcdr ,n ,seq)
                        ,store)
                     (aset ,seq ,n ,store)))
                do args))
```

ここで、単なる `get` ではなく、 `function-get` を使うと、シンボルが`autoload` 宣言された関数で、かつ未定義だった場合、自動的にライブラリを読み込ませることができます。

一般に、データ構造にアクセスする関数を汎変数にできます。以下に、Emacs25 における汎変数に対応する関数を、データ構造毎に分類した一覧を示します。


## 汎変数に対応する関数の一覧

-   リスト・配列
    -   aref
    -   caar
    -   cadr
    -   car
    -   cdar
    -   cddr
    -   cdr
    -   cl-eighth
    -   cl-fifth
    -   cl-ninth
    -   cl-seventh
    -   cl-sixth
    -   cl-subseq
    -   cl-tenth
    -   elt
    -   nth
    -   nthcdr

-   シンボルのプロパティ
    -   cl-get
    -   get

-   文字列
    -   substring

-   バッファ
    -   buffer-file-name
    -   buffer-local-value
    -   buffer-modified-p
    -   buffer-name
    -   buffer-string
    -   buffer-substring
    -   current-buffer
    -   current-column

-   face
    -   documentation-property
    -   face-background
    -   face-background-pixmap
    -   face-font
    -   face-foreground
    -   face-underline-p

-   キーマップ
    -   current-global-map
    -   current-input-mode
    -   current-local-map
    -   global-key-binding
    -   keymap-parent
    -   local-key-binding

-   ウィンドウ
    -   current-window-configuration
    -   window-buffer
    -   window-dedicated-p
    -   window-display-table
    -   window-height
    -   window-hscroll
    -   window-parameter
    -   window-point
    -   window-start
    -   window-width
    -   selected-window

-   オブジェクト配列
    -   default-value
    -   symbol-function
    -   symbol-plist
    -   symbol-value

-   alist
    -   alist-get （注：Emacs 25 より対応）

-   ファイル
    -   default-file-modes
    -   file-modes
    -   visited-file-modtime

-   フレーム
    -   frame-height
    -   frame-parameter
    -   frame-parameters
    -   frame-visible-p
    -   frame-width
    -   screen-height
    -   screen-width
    -   selected-frame
    -   selected-screen
    -   terminal-parameter

-   レジスタ
    -   get-register

-   環境変数
    -   getenv

-   ハッシュテーブル
    -   gethash

-   ポイント・マーカ
    -   mark
    -   mark-marker
    -   marker-position
    -   point
    -   point-marker
    -   point-max
    -   point-min

-   オーバレイ
    -   overlay-end
    -   overlay-get
    -   overlay-start

-   プロセス
    -   process-buffer
    -   process-filter
    -   process-get
    -   process-sentinel

-   プログラム構造・特殊形
    -   apply
    -   cond
    -   cons
    -   eq
    -   if
    -   let
    -   let\*
    -   logand

-   その他
    -   gv-deref
    -   match-data
    -   mouse-position
    -   read-mouse-position
    -   standard-case-table
    -   syntax-table
    -   x-get-secondary-selection

以下はEmacs標準の各パッケージで定義される汎変数です。

-   frameset.el
    -   frameset-prop
-   scroll-bar.el
    -   get-scroll-bar-mode
-   image-mode.el
    -   image-mode-window-get
-   mailheader.el
    -   mail-header
-   quickurl.el
    -   quickurl-url-comment
    -   quickurl-url-url
-   url/url-parse.el
    -   url-port
-   winner.el
    -   winner-active-region

汎変数の中には、興味深いものもあります。たとえば、 `(setf (eq a 4) t)`では、 `a` に 4 が代入されます。このような形で引数に関与するような汎変数は、従来の CL パッケージ `define-setf-expander` で提供される手法では実現は困難でした。


# 汎変数マクロの例

汎変数を利用するマクロの例として、もっとも単純な `setf` および `callf`の実装を示します。（実際は繰り返し処理等があるのでもう少し複雑です。）

```el
(defmacro setf-simple (place val)
  (gv-letplace (getter setter) place
    (funcall setter val)))
```

```el
(defmacro callf-simple (func place val)
  (gv-letplace (getter setter) place
    (funcall setter (list func getter val))))
```

マクロは（実行時ではなく）コンパイル時に評価され、式を返します。通常の関数とは異なり、「実行時にどのような処理をさせる『式』を出力するのか」を記述します。

マクロ `gv-letplace` は、 `place` に対応する汎変数のラムダ関数を`gv-expander` シンボルから取得します。また、本体 (body) では、２変数(ここでは `getter` および `setter`) に対して、汎変数を処理する式を生成するプログラムを書きます。

変数 `getter` には、汎変数の値を取得する式が、変数 `setter` には、「設定させたい値が出力される式」を引数にして呼び出すことで、実行時に値を設定する式を返す関数が入ります。（分かりにくい場合は、上述の`callf-simple` の実装を確認ください。）

（マクロ `rotatef` 等は、マクロの中で一時変数等を作りますが、静的スコープで動作するため、これらの変数は `funcall` で呼ばれる `setter` には影響を与えません。）

この仕組みによって、「データ構造にアクセスする式の生成」（汎変数）と、「データを処理する式の生成」（汎変数マクロ）が分離され、お互いが協調することで、汎変数を処理する式の全体が生成されます。


# 汎変数の定義方法

汎変数を定義するには、関数としての実装の他に、コンパイル時に評価される関数を関数名シンボルの `gv-expander` プロパティに `put` します。 `elt`の例は上に示した通りです。

汎変数は非常に柔軟な設定ができますが、単純に設定用の式をマクロとして入れる場合は、 `gv-define-setter` という便利なマクロが用意されています。

先ほどの `elt` の例を以下に示します。

```el
(gv-define-setter elt (store seq n)
  `(if (listp ,seq) (setcar (nthcdr ,n ,seq) ,store)
     (aset ,seq ,n ,store)))
```

これは、配列 `seq` の `n` 番目に `store` を設定する式がクォートされたものです。 `gv-define-setter` は、この式に対して、『汎変数の引数の式を「評価し、 `let` 文のローカル変数に退避する式」を生成し、そのローカル変数の値を `n` に代入した形で上記の式を展開する関数』を生成して関数の`gv-expander` プロパティに putします。 (`let` 文の生成には`gv--defsetter` という補助関数が使われます。上記の例を参照。)

多くの汎変数は `gv-define-simple-setter` や `gv-define-setter` で設定できますが、マクロや特別形式、または上述の `eq` のような、引数に配慮が必要な式の汎変数には、手動で `gv-expander` を記述するものもあります。


# 汎変数の仕組み

ここでは、 `nthcdr` 関数の `gv-expander` プロパティ値を確認してみましょう。

```el
(pp (function-get 'nthcdr 'gv-expander t)) ↩
(closure
 (t)
 (do n place)
 (macroexp-let2 nil idx n
   (gv-letplace (getter setter) place
     (funcall do `(nthcdr ,idx ,getter)
              (lambda (v)
                `(if
                    (<= ,idx 0)
                    ,(funcall setter v)
                  (setcdr (nthcdr (1- ,idx) ,getter) ,v)))))))
```

オブジェクト `closure` は、文脈スコープにおいて定義されるラムダ関数を表現するオブジェクトで、第三要素の `(do n place)` が引数になります。

この `gv-expander` プロパティに入れられた関数の引数 `do` が汎変数の肝となります。Emacs Lisp の型を、

-   変数名: 型
-   関数名: ((引数1, 引数2, &#x2026;) -> 返値)

で表現すると、 `gv-expander` の型は以下のように表現されます。

    gv-expaner: (do: ((getter: Exp, setter: ((store: Exp) -> Exp)) ->Exp) -> Exp)

これを外から分解してみると、

1.  関数 `gv-expander` は、 `do` という関数を引数に取り、式を返す。
2.  関数 `do` は、 `getter` という式と、 `setter` という関数を引数にとって、式を返す。
3.  関数 `setter` は、 `store` という式を引数にとって、式を返す

という３段構造になっています。「汎変数を利用する側」は、「どう利用するか（式を生成するか）」を `do` に入れ、それを汎変数側に渡します。「汎変数側」は、 `do` に対して、汎変数の値を取り出す式 (`getter`) と、汎変数に値を設定するための式を生成する関数 (`setter`) を提供することで、全体の処理式を生成します。

この `do` の部分を、分かりやすく形式化したものが、 `gv-letplace` 関数です。 `gv-letplace` は、引数 (`getter`, `setter`) および本体 `body` をもとに、 `(lambda (getter setter) body)` というラムダ関数を生成し、これを `do` として、 `place` の `gv-expander` から取り出された関数に適用することで、式を作り出します。

前出の `macroexp-let2` 関数は、コンパイル（マクロ展開）時に出力される式において、引数が副作用を起こさないように、一旦、引数を評価して `let`文のローカル変数に保存するための式を生成します。たとえば、

```el
(incf (gethash (somefunc) table)
```

という式を考えてみます。 `incf` は、汎変数から値を取り出し、１を加えて再びそれを汎変数に設定するので、都合２回、汎変数 `(gethash ...)` を呼び出します。その際、 `gethash` の中にある `(somefunc)` が２回、式として展開されるならば、それは評価時にプログラマの意図しないものとなるでしょう。

そのため、汎変数の引数は１度評価したらその結果を別変数に保存するために`let` 文を生成し、その中で局所定義された変数を利用した式を生成します。

また、 `gv-expander` の中でも、 `gv-letplace` が利用されていることにも注目下さい。このように、汎変数の引数にも汎変数が使えるため、ネストして汎変数が利用できます。

Emacs 24.2 より前は、汎変数は cl パッケージにおける`define-setf-expander` によって定義されていました。ここでは、上記の目的を達成するために、(1) 汎変数の引数式を処理する式、 (2)その結果を一時的に代入する変数、そしてその変数を使って汎変数の値を (3) 取得したり(4) 設定したりする式、(5) 実行結果を格納する変数、の総計５つをばらばらに指定していました。これは、わかりにくい上に不自由な構造になっていました。

Emacs 24.3 から導入された gv.el は、クロージャと高階関数を活用することで、コンパイル時に汎変数の引数も汎変数側で自由に処理できるようにしています。


# 最後に

Lispのマクロは、関数とは異なる次元でコードの抽象度を高める興味深い機能です。コンパイル時に評価される式と、実行時に評価される式の両方を同じ言語で継ぎ目なく書けるのは、Lisp系言語の大きな利点の一つです。 Emacs24.3 の汎変数は、マクロと高階関数を活用することで、巧妙で、かつ分かり易い実装を実現します。gv.el は短いながらも妙味がありますので、興味のある方には眺めてみても良いかもしれません。

（追記： `setf` を除く汎変数マクロは現状では cl パッケージに入っているので、先頭に `cl-` 前置詞を付加しないと、コンパイル時に警告される可能性があります。）