| [ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
kill リングは rotate-yank-pointer の働きによってリングの形態に変
換されたリストである。yank 及び yank-pop コマンドは
rotate-yank-pointer 関数を利用している。この Appendix では、
yank や yank-pop と共に、rotate-yank-pointer 関数も
説明することにする。
B.1 関数 rotate-yank-pointerリストの中でのポインタの移動と巡回 B.2 yank切り取ったテキストを貼り付ける B.3 yank-popポインタが指しているテキストを貼り付ける
rotate-yank-pointer
rotate-yank-pointer 関数は kill-ring-yank-pointer が指す
kill リングの要素を変更する。例えば、今まで
kill-ring-yank-pointer が二番目の要素を指していたなら、それを三番
目の要素に変更する。
これが rotate-yank-pointer のコードである。
(defun rotate-yank-pointer (arg)
"Rotate the yanking point in the kill ring."
(interactive "p")
(let ((length (length kill-ring)))
(if (zerop length)
;; then-part
(error "Kill ring is empty")
;; else-part
(setq kill-ring-yank-pointer
(nthcdr (% (+ arg
(- length
(length
kill-ring-yank-pointer)))
length)
kill-ring)))))
|
この関数は複雑そうに見えるが、いつも通り幾つかの部分に分けて考えると、 すっきりと理解出来る。最初に骨組みを見てみよう。
(defun rotate-yank-pointer (arg)
"Rotate the yanking point in the kill ring."
(interactive "p")
(let 変数リスト
本体...)
|
この関数は arg という引数を一つ取る。また、簡単な説明文字列がつい
ており、小文字の `p' によりインタラクティブ宣言されている。これは、
引数が処理された前置引数であり、数として関数に渡されなければならないこと
を意味している。
関数定義の本体は let 式であり、これは変数リスト varlist と
本体部分からなる。変数は length と呼ばれ、kill リングの要素の数に
バインドされる。この数は、length という関数によって調べられる。
(この関数が、変数 length と同じ名前であることに注意しよう。にもか
かわらず一方では関数名として使われ、もう片方は変数名として使われている。
この二つはきちんと区別されている。同様に、英語を話す人々は `ship'
という単語の意味を "I must ship this package immediately." という時と "I
must get aboard the ship immediately." という時で区別している。)
関数 length はリストの中の要素の数を教えてくれるものである。従っ
て、(length kill-ring) は kill リングの中の要素の数を返すことにな
る。
B.1.1 rotate-yank-pointerの本体
rotate-yank-pointer の本体
rotate-yank-pointer は let 式であり、let の本体は、
if 式である。
この if 式は、kill リングに何か要素があるかどうかを見るためのもの
である。もし kill リングが空なら error 関数がこの関数の評価を止め
て、エコー領域にメッセージを表示する。一方、もし kill リングが空でなかっ
た場合は、この関数の残りの仕事が実行される。
以下が、この if 式の if-part と then-part である。
(if (zerop length) ; if-part
(error "Kill ring is empty") ; then-part
...
|
もし kill リングに何もなければその長さは零であるから、ユーザに対して
`Kill ring is empty' というエラーメッセージが表示される。上の
if 式では、値が零かどうかの判定に zerop を使っている。これ
はテスト対象が零であれば真を返す関数である。もし zerop テストが真
であれば、if 式の then-part が評価される。then-part は関数
error から始まるリストである。この関数は message 関数と似
ていて(関数 message, 参照)、一行メッセージをエコー
領域に表示するのだが、それだけではなくて error が埋め込まれている
関数の評価を停止する働きも持っている。今の場合、もし kill リングの長さが
零であれば関数の残りの部分は評価されないということになる。
(個人的な意見では、少なくとも人間にとっては、この関数に `error' という用 語を使うのはちょっと誤解を招きやすい気がする。例えば `cancel' という用語 の方がよりしっくりくる。勿論厳密に言えば長さを持たないリストの要素を指す ことは出来ないし、ましてや指す位置を巡回させることなど出来ない。だから、 計算機の視点に立てば `error' という言葉は全く正しい。しかし人間の立場か らすると、こういったことをしたいのは kill リングが空かそうでないかを見る 場合だけである。これは一種の調査でしかない。
人間の視点から見ると、調査や発見という行為は必ずしもエラーではない。従っ て、たとえ計算機の世界の奥底にいる場合でもエラーと呼ぶべきではない。もし そうだとしたら、Emacs のコードは、正しい態度で自らの環境を調査している人 がエラーを犯していると言っていることになってしまうからだ。これではまずい。 いくら、計算機の方がエラーが起きた場合と同じステップを取ることになるとは いっても、`cancel' の方がより正しい語感を持っていると言えよう。)
if式の else-part剰余関数 %rotate-yank-pointerの中での%の利用最後の要素を指している場合
if 式の else-part
if 式の else-part は kill リングが空でない場合の
kill-ring-yank-pointer の設定に費やされている。コードは以下の通り
である。
(setq kill-ring-yank-pointer
(nthcdr (% (+ arg
(- length
(length kill-ring-yank-pointer)))
length)
kill-ring)))))
|
これにはちょっと説明が必要だろう。まず kill-ring-yank-pointer が
kill リングの何番目かの CDR に設定されるべきであるというのはいいだ
ろう。これには以前説明した nthcdr 関数を使う。
(8.5 copy-region-as-kill, 参照。) しかし、実際にはどうしたらいいのだろ
うか。
細かいコードを見る前に、まず rotate-yank-pointer の目的を考えてみ
よう。
rotate-yank-pointer 関数は、kill-ring-yank-pointer が指す
場所を変更する。もし kill-ring-yank-pointer がリストの最初の要素
を指している状態から出発した場合、rotate-yank-pointer を呼び出す
と、これが二番目の要素を指すことになる。そして更に
rotate-yank-pointer を呼び出すと、三番目に移る。(また、
rotate-yank-pointer に1より大きな引数を与えて呼び出すと、その数だ
け指す位置が移動される。)
rotate-yank-pointer 関数は setq を使って
kill-ring-yank-pointer が指す位置をリセットしている。もし
kill-ring-yank-pointer が kill リングの最初の要素を指していたなら、
単純な場合、rotate-yank-pointer 関数は二番目の要素を指さなければ
ならない。別の言い方をすると、kill-ring-yank-pointer は kill リン
グの CDR に等しい値に再設定されなければならないのである。
即ち、
(setq kill-ring-yank-pointer
("some text" "a different piece of text" "yet more text"))
(setq kill-ring
("some text" "a different piece of text" "yet more text"))
|
という状況では、コードは
(setq kill-ring-yank-pointer (cdr kill-ring)) |
となるべきである。結果として、kill-ring-yank-pointer は次のように
なっていなければならない。
kill-ring-yank-pointer
=> ("a different piece of text" "yet more text"))
|
実際の setq 式では nthcdr 関数が使われている。
以前見たように (7.3 nthcdr, 参照)、nthcdr 関数はリストの
CDR を繰り返し取ることによって動作する。これは CDR の CDR
の ... の CDR を取る、ということである。
次の二つのS式は同じことをやっている。
(setq kill-ring-yank-pointer (cdr kill-ring)) (setq kill-ring-yank-pointer (nthcdr 1 kill-ring)) |
しかし rotate-yank-pointer 関数では、nthcdr の最初の引数の
所に何やら算数が使われていて、複雑そうに見える。
(% (+ arg
(- length
(length kill-ring-yank-pointer)))
length)
|
これを理解するには、いつもの通りまずネストの一番深い所にあるS式から順に 見ていく必要がある。
もっとも深い所にあるS式は (length kill-ring-yank-pointer) である。
これは、現在の kill-ring-yank-pointer の長さを調べるものだ。
(kill-ring-yank-pointer は値がリストであるような変数の名前である
ことを思い出そう。)
この長さを測定するS式は、次のS式の中に埋め込まれている。
(- length (length kill-ring-yank-pointer)) |
このS式の中の最初の length は、この関数の最初の let 式の
局所変数の宣言の部分で kill リングの長さに設定された変数である。(この
length という変数は length-of-kill-ring と名付けた方が解り
やすいのではないかという人も居るかもしれない。しかし、ここでやっているよ
うに小さい部分に切り分けたりせずに、関数全体を眺めてみれば、短いが故に面
倒でないということが実感出来るだろう。)
従って、(- length (length kill-ring-yank-pointer)) という行で、
kill リングの長さと kill-ring-yank-pointer という名前のリストの長
さの差を知ることが出来る。
これが rotate-yank-pointer 関数の中にどう組み込まれているかを見る
ために、まず kill-ring-yank-pointer が kill リングの最初の要素を
指している場合、つまり、kill リング全体である場合に、
rotate-yank-pointer を引数1で呼び出すとどうなるかを分析してみるこ
とにしよう。
この場合は、変数 length と (length kill-ring-yank-pointer)
の長さは同じである。というのは、変数 length の値は kill リングの
長さであり、kill-ring-yank-pointer は kill リング全体を指している
からである。結果として、
(- length (length kill-ring-yank-pointer)) |
の値は零になる。引数として1を与えているので、
(+ arg (- length (length kill-ring-yank-pointer))) |
全体の値は1になる。
そうすると、nthcdr の引数としては次の式の値が渡されることになる。
(% 1 length) |
%
(% 1 length) を理解するには、% が何かを知らねばならない。
説明文字列によると (これは C-h f % RET とタイプすると
見ることが出来る)、% 関数は最初の引数を二番目の引数で割った余りを
返す関数であることが分る。例えば5を2で割った余りは1である。(2は5の中に二
つ含まれていて、残りが1ということである。)
あまり算数を知らない人の中には、ある数をその数よりも大きな数で割ることが 出来て、しかも余りまで求まると聞くとびっくりする人も多い。上の例では5を2 で割ったのだが、これを逆にして、2を5で割るとどうなるだろうか。もし分数を 使えれば、答えは明らかに2/5もしくは0.4である。が、ここでは整数しか使えな いので、これとは違う答えにならなければならない。明らかに5は2の中に零個し か含まれていない。が、残りはいくつだろう。答えを知るために、子供の頃から 親しんでいる場合を考えてみよう。
これと全く同じように考えれば、
という具合になる。
従って、今回のコードでは、もし length の値が5であれば、
(% 1 5) |
を評価した結果は1になる。(これは、カーソルを上の式の最後に持っていって C-x C-e とタイプすれば確かめられる。ちゃんとエコー領域に1が表示さ れるはずだ。)
rotate-yank-pointer の中での % の利用
kill-ring-yank-pointer が kill リングの最初を指しており、かつ
rotate-yank-pointer の引数として1が渡された場合、% 式が返
す値は1になる。
(- length (length kill-ring-yank-pointer))
=> 0
|
故に、
(+ arg (- length (length kill-ring-yank-pointer)))
=> 1
|
であり、結果として length の値に関係無く
(% (+ arg (- length (length kill-ring-yank-pointer)))
length)
=> 1
|
となる。
以上から、この場合の setq kill-ring-yank-pointer 式は、
(setq kill-ring-yank-pointer (nthcdr 1 kill-ring)) |
のようになる。これを見れば、やっていることは一目瞭然だ。
kill-ring-yank-pointer の指す位置を kill リングの最初の要素ではな
く二番目の要素にセットするのである。
また、rotate-yank-pointer に渡す引数を2にすれば
kill-ring-yank-pointer は (nthcdr 2 kill-ring) にセットさ
れることも明らかだろう。他の引数を与えた場合も同様である。
同じように、もし kill-ring-yank-pointer が kill リングの二番目の
要素からスタートした場合、その長さは kill リングの長さより1少ない値にな
り、また余りの計算は (% (+ arg 1) length) である。従って、引数1で
rotate-yank-pointer を評価すると、kill-ring-yank-pointer
は kill リングの二番目の要素から三番目の要素を指すように変更される。
最後の問題は、kill-ring-yank-pointer が kill リングの最後の要素を
指していた場合にどうなるかである。kill リングにはもう何も残っていないの
で、rotate-yank-pointer は何も指さない状態になるのだろうか。答え
は No である。実際にはもっと都合の良い結果になる。
kill-ring-yank-pointer は kill リングの最初の要素を指すようにセッ
トされるのである。
コードを見てどのように動作するのかを見てみることにしよう。取り敢えず
kill リングの長さは5とし、rotate-yank-pointer に渡す引数は1である
とする。kill-ring-yank-pointer が kill リングの最後の要素を指して
いる場合、その長さは1である。コードは次のようになっている。
(% (+ arg (- length (length kill-ring-yank-pointer))) length) |
上の式の中の変数を実際の数値で置き換えると、式は次のようになる。
(% (+ 1 (- 5 1)) 5) |
このS式をもっとも内側の式から外側に向かって見ていこう。(- 5 1)
の値は4である。(+ 1 4) という和は5になる。そして、5を5で割った余
りは0である。従って rotate-yank-pointer では、
(setq kill-ring-yank-pointer (nthcdr 0 kill-ring)) |
というS式を評価することになる。これは、kill-ring-yank-pointer を
kill リングの最初の要素を指すようにセットするものである。
従って、rotate-yank-pointer を続けて呼び出すと、
kill-ring-yank-pointer を kill リングの最初の値から順に移動してい
き、最後の要素まで来たらまた最初に戻るというふうになる。そしてこれこそが
kill リングがリングと呼ばれる所以である。最後まで来たらまた最初に戻るの
で、あたかもリストには終わりがないように見えるのだ! (訳註:原文では最後
に "And what is a ring, but an entity with no end?" という文が続く。これ
はどう訳したものだろう?)
yank
rotate-yank-pointer について学んだ後では、yank 関数のコー
ドは全く易しく見えるだろう。これには一箇所だけトリッキーな部分がある。
rotate-yank-pointer に渡す引数の計算の所だ。
コードは以下の通りである。
(defun yank (&optional arg)
"Reinsert the last stretch of killed text.
More precisely, reinsert the stretch of killed text most
recently killed OR yanked.
With just C-u as argument, same but put point in front
(and mark at end). With argument n, reinsert the nth
most recently killed stretch of killed text.
See also the command \\[yank-pop]."
(interactive "*P")
(rotate-yank-pointer (if (listp arg) 0
(if (eq arg '-) -1
(1- arg))))
(push-mark (point))
(insert (car kill-ring-yank-pointer))
(if (consp arg)
(exchange-point-and-mark)))
|
このコード全体を眺めてみると、最後の数行だけでもほぼ十分なことが分る。ま
ずマークがプッシュされる。つまり、位置が記憶される。そして、
kill-ring-yank-pointer が指すリストの最初の要素 (即ち CAR)
が挿入される。更に、もし関数に渡された引数がコンスセルであれば、ポイント
とマークが交換され、ポイントは挿入されたテキストの最後ではなく最初の位置
に置かれる。このオプションは説明文字列の中で説明されている。関数そのもの
はオプション "*P" でインタラクティブ宣言されている。これは、書き
込み不可のバッファでは動作せず、未処理の前置引数が関数に渡されるという指
定である。
引数の渡し方 rotate-yank-pointerに引数を渡す負の引数を渡すとどうなるか
yank で難しいのは rotate-yank-pointer に渡される引数の計算
を理解する部分である。幸いなことに、これは見た目程難しいわけではない。
やっていることは、二つある if 式の片方ないしは両方を評価し、結果
として得られた数値を rotate-yank-pointer に引数として渡すというこ
とである。
コードをコメント付きで見やすいように書くと、次のようになる。
(if (listp arg) ; if-part
0 ; then-part
(if (eq arg '-) ; else-part, 内部の if
-1 ; 内部の if の then-part
(1- arg)))) ; 内部の if の else-part
|
このコードは二つの if 式からなる。一つはもう一方の else-part になっ
ている。
最初の外側の if 式は yank に渡された引数がリストであるかど
うかのテストである。奇妙なことに、yank が引数無しで呼び出された場
合にはこれは真になる。理由は、この場合省略可能な引数の値として
nil が渡されるのだが、(listp nil) は真になるからである!
従って、もし yank を引数無しで呼び出すと yank の内部の
rotate-yank-pointer には引数として零が渡される。これは、
kill-ring-yank-pointer が指すリストは変化せず、その最初の要素が挿
入されることを意味する。これは、期待通りの動作である。同様に、もし
yank が引数 C-u で呼び出されたとすると、これもリストだと見
倣され、またもや rotate-yank-pointer には零が渡される。(C-u
は処理されない前置引数 (4) を生成するため。) 同時に、この引数はこ
の関数の後の部分でコンスセルと見倣されるので、ポイントは挿入されたテキス
トの始めに、マークは終わりに置かれる。(interactive の P と
いう引数は、オプションの引数が渡されなかった場合や C-u が渡された
場合に上で述べたような値を与えるように設計されている。)
ということで、引数無しの場合や引数が C-u の場合は、外側の
if 式の then-part が扱う。そして、その他の場合は else-part が扱う。
else-part はそれ自身が if 式になっている。
内部の if 式は、引数が負の符号かどうかを判定するテストである。(こ
れは META キーと - キーを同時に押すか、ESC キーを押し
てから - キーを押すと発生する。) この場合、
rotate-yank-pointer 関数に渡される引数は -1 になる。これは、
kill-ring-yank-pointer の指す位置を一つ手前に戻すことになり、期待
通りの動作となる。
もし内部の if 式の真偽テストが偽であったなら (つまり引数が負の符
号でなければ)、このS式の else-part が評価される。これは、(1-
arg) という式である。二つの if 式から、この場合になるのは引数が
正の数であるか、もしくは (単に負の符号だけではない) 負の数である場合のみ
である。(1- arg) はその数から1を引いた値を返す。(1- は引数
を1減らす関数である。) これは、もし yank の引数が1だったとすると、
それが0になるということである。従って、kill-rotate-yank-pointer
の最初の要素がヤンクされる。これも期待通りである。
最後に、幾つかの疑問が湧く。もし「剰余関数」% や nthcdr
関数に負の引数を与えたとするとどうなるだろうか。
答えはちょっと実験してみれば分る。(% -1 5) を評価すると、負の引数
が返る。また、nthcdr が負の引数と共に呼び出されると、最初の引数が
零であった場合と同じ値が返る。これは次のコードを評価すれば確かめられる。
以下では、`=>' の後に書かれているものがその前のコードを評価
した結果である。評価するには、いつものようにコードの直後にカーソルを置い
て C-x C-e (eval-last-sexp) とタイプすると良い。これは、こ
の文書を GNU Emacs の Info の中で読んでいる場合にはそのまま実行出来る。
(% -1 5)
=> -1
(setq animals '(cats dogs elephants))
=> (cats dogs elephants)
(nthcdr 1 animals)
=> (dogs elephants)
(nthcdr 0 animals)
=> (cats dogs elephants)
(nthcdr -1 animals)
=> (cats dogs elephants)
|
従って、負の符号や負の数が yank に渡された場合には、
kill-rotate-yank-pointer は逆向きに周り、リストの最初に辿りつくと、
そのまま止まるという結果になる。正の引数を与えた場合のように、一番最後ま
で行くとまた最初に戻るというふうにはならず、そこで止まってしまうわけである。
これは合理的な動作である。ヤンクする際は、直前に切り取ったテキストをヤン
クしたい場合が最も多く、普通は30回も前に kill したテキストを挿入しようと
は思わないだろう。だから、リングの中を順に進んでいって一番最後まで行くこ
とはあっても、後から最初の方に戻ろうとして、また一番最後に戻ってしまうな
んてことは、あって欲しくないに違いない。
ついでにいっておくと、負の符号がついた数が yank に引数として渡さ
れる場合は、全て -1 として扱われる。これは、明らかにプログラムを
単純にするためのものである。kill リングの中を最初の方に向かって一度に二
つ以上もジャンプする必要はないし、負の数の絶対値を決定する関数を書くより
もこっちの方がずっと簡単である。
yank-pop
yank を理解してしまえば、yank-pop 関数は簡単に理解出来る。
スペースの節約のために説明文字列を省くと、コードは次の通りである。
(defun yank-pop (arg)
(interactive "*p")
(if (not (eq last-command 'yank))
(error "Previous command was not a yank"))
(setq this-command 'yank)
(let ((before (< (point) (mark))))
(delete-region (point) (mark))
(rotate-yank-pointer arg)
(set-mark (point))
(insert (car kill-ring-yank-pointer))
(if before (exchange-point-and-mark))))
|
この関数は、小文字の `p' でインタラクティブ宣言されている。従って、
前置引数は処理されてから関数に渡される。このコマンドは、直前にヤンクを行っ
た場合にのみ有効である。そうでなければ、エラーメッセージが出される。この
チェックは変数 last-command で行う。(これについては他の所で説明し
た。8.5 copy-region-as-kill, 参照。)
let 式では、変数 before を、ポイントがマークの前にあるか後
にあるかによって真もしくは偽にセットする。そして、ポイントとマークの間の
リージョンを削除する。これは、直前のヤンクによって挿入されたリージョンで
あり、置き換えようとしている部分である。次に
kill-ring-yank-pointer を回して直前に挿入したテキストがまた挿入さ
れないようにする。マークは新しいテキストが挿入されるべき部分にセットされ、
kill-ring-yank-pointer が指すリストの最初の要素が挿入される。その
結果、ポイントは新しく挿入されたテキストの直後に置かれる。ここで、もし前
回のヤンクでポイントが挿入されたテキストの前に置かれていたならポイントと
マークを交換して、ポイントを今回挿入したテキストの最初の位置に移動する。
これがこの関数でやっていることの全てである!
| [ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |