[ << ] | [ >> ] | [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] | [ ? ] |