[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

B. Kill リングの扱い

kill リングは rotate-yank-pointer の働きによってリングの形態に変 換されたリストである。yank 及び yank-pop コマンドは rotate-yank-pointer 関数を利用している。この Appendix では、 yankyank-pop と共に、rotate-yank-pointer 関数も 説明することにする。

B.1 関数 rotate-yank-pointer  リストの中でのポインタの移動と巡回
B.2 yank  切り取ったテキストを貼り付ける
B.3 yank-pop  ポインタが指しているテキストを貼り付ける


B.1 関数 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 の本体  


B.1.1 rotate-yank-pointer の本体

rotate-yank-pointerlet 式であり、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?" という文が続く。これ はどう訳したものだろう?)


B.2 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) を生成するため。) 同時に、この引数はこ の関数の後の部分でコンスセルと見倣されるので、ポイントは挿入されたテキス トの始めに、マークは終わりに置かれる。(interactiveP と いう引数は、オプションの引数が渡されなかった場合や 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 リングの中を最初の方に向かって一度に二 つ以上もジャンプする必要はないし、負の数の絶対値を決定する関数を書くより もこっちの方がずっと簡単である。


B.3 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] [ ? ]

This document was generated by Matsuda Shigeki on April, 10 2002 using texi2html