[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
キルリングはリストであるが、 関数rotate-yank-pointer
の働きでリングに変換されている。 コマンドyank
とyank-pop
は、 関数rotate-yank-pointer
を使っている。 本付録では、関数rotate-yank-pointer
とともに、 コマンドyank
とyank-pop
を説明する。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
rotate-yank-pointer
rotate-yank-pointer
"へのコメント(無し)
関数rotate-yank-pointer
は、kill-ring-yank-pointer
が指す キルリングの要素を変更する。 たとえば、kill-ring-yank-pointer
が第2要素を指していたら 第3要素を指すように変更する。
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) ;; 真の場合の動作 (error "Kill ring is empty") ;; 偽の場合の動作 (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 変数リスト 本体...) |
この関数は、1つの引数、arg
を取る。 短い説明文字列があり、小文字の`p'を指定したinteractive
がある。 これは、前置引数を処理した数を引数として関数に渡すことを意味する。
関数定義の本体はlet
式であり、これには変数リストと本体がある。
let
式は、この関数内部でのみ使える変数を宣言する。 この変数は、length
であり、キルリング中の要素数に束縛される。 関数length
を呼び出してこれを行う (この関数は変数length
と同じ名前である。 しかし、一方は関数の名称としての使い方であり、 他方は変数の名称としての使い方である。 この2つはまったく異なる。 同様に、英語の話者は、 『I must ship this package immediately.』と 『I must get aboard the ship immediately.』との`ship'の意味を 区別できる)。
関数length
は、リスト内の要素の個数を返すので、 (length kill-ring)
は、キルリングにある要素の個数を返す。
B.1.1 rotate-yank-pointer
の本体The Body of rotate-yank-pointer
.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
rotate-yank-pointer
の本体rotate-yank-pointer
の本体"へのコメント(無し)
rotate-yank-pointer
の本体はlet
式であり、 let
式の本体はif
式である。
if
式の目的は、キルリングに何かがあるかどうかを調べることである。 キルリングが空ならば、関数error
が関数の評価を停止し、 エコー領域にメッセージを表示する。 一方、キルリングに何かがあれば、関数の処理を実施する。
if
式の判定条件と真の場合の動作をつぎに示す。
(if (zerop length) ; 判定条件 (error "Kill ring is empty") ; 真の場合の動作 ... |
キルリングに何もなければ、その長さは0であるはずなので、 ユーザーにエラーメッセージ`Kill ring is empty'を表示する。 if
式では関数zerop
を用いるが、これは値が0ならば真を返す。 zerop
が真を返したならば、if
の真の場合の動作が評価される。 真の場合の動作は、関数error
で始まるリストである。 これは関数message
(see 節 1.8.5 関数message
)に似ており、 1行のメッセージをエコー領域に表示する。 しかし、メッセージの表示に加えて、 error
はこれを含んだ関数の評価を停止させる。 この場合は、キルリングの長さが0ならば、 関数の残りの部分は評価されないことを意味する。
(筆者の意見では、この関数の名前に「error」を使うことは、 少なくとも人間にとっては、少々誤解を招く。 よりよい名前は「cancel」であろう。 厳密にいえば、長さが0のリストを指すポインタをつぎの要素を 指すように巡回できないので、コンピュータの視点からは 「error」は正しい用語である。 しかし、キルリングが満杯か空かを調べるだけでも、 人間はこの種のことを試せるものと考える。 これは、探査行為である。)
(人間の視点からは、探査行為や発見行為は、必ずしもエラーではなく、 したがって、たとえコンピュータ内部であっても、「error」と呼ぶべきではない。 Emacsのコードでは、高潔に振舞っている人間がその環境を探査していて エラーを引き起こしたということになる。 これは残念である。 エラーがあった場合にはコンピュータは同じ手順を踏むのであるが、 「cancel」のような用語のほうが、はっきりと意味を表す。)
if
式の偽の場合の動作The else-part of the if
expression.剰余関数 %
The remainder, %
, function.rotate-yank-pointer
における%
の利用Using %
inrotate-yank-pointer
.最後の要素を指す Pointing to the last element.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
if
式の偽の場合の動作if
式の偽の場合の動作"へのコメント(無し)
if
式の偽の場合の動作は、キルリングに何かがあるときに kill-ring-yank-pointer
の値を設定することに費される。 コードはつぎのとおりである。
(setq kill-ring-yank-pointer (nthcdr (% (+ arg (- length (length kill-ring-yank-pointer))) length) kill-ring))))) |
これには説明が必要であろう。 明らかに、kill-ring-yank-pointer
には、 前節で説明した関数nthcdr
を使って キルリングのどこかのCDRを設定する(See 節 8.5 copy-region-as-kill
)。 これをどうやって行っているのであろう?
コードの詳細を見るまえに、関数rotate-yank-pointer
の目的を考えてみよう。
関数rotate-yank-pointer
は、 kill-ring-yank-pointer
が指すものを変更する。 kill-ring-yank-pointer
がリストの先頭要素を指している場合に rotate-yank-pointer
を呼び出すと、2番目の要素を指すように変わる。 kill-ring-yank-pointer
が2番目の要素を指している場合に rotate-yank-pointer
を呼び出すと、3番目の要素を指すように変わる (また、rotate-yank-pointer
に1より大きな引数を与えると、 その数だけポインタを進める)。
関数rotate-yank-pointer
は、 kill-ring-yank-pointer
が指すものを再設定するためにsetq
を使う。 kill-ring-yank-pointer
がキルリングの第1要素を指している場合、 もっとも簡単な場合であるが、関数rotate-yank-pointer
は、 kill-ring-yank-pointer
が2番目の要素を指すようにする。 いいかえれば、kill-ring-yank-pointer
には、キルリングの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
を使ってこれを行う。
すでに見たように(see 節 7.3 nthcdr
)、 関数nthcdr
は、リストのCDRを繰り返し取る。 CDRのCDRのCDR ...を取る。
つぎの2つの式は同じ結果になる。
(setq kill-ring-yank-pointer (cdr kill-ring)) (setq kill-ring-yank-pointer (nthcdr 1 kill-ring)) |
しかし、関数rotate-yank-pointer
では、nthcdr
の第1引数は、 多くの算術を含んだ複雑に見える式である。
(% (+ arg (- length (length kill-ring-yank-pointer))) length) |
いつものように、もっとも内側の式から始めて、外側に向かって調べる必要がある。
もっとも内側の式は、(length kill-ring-yank-pointer)
である。 これは、kill-ring-yank-pointer
の現在の長さを調べる (kill-ring-yank-pointer
は、その値がリストである変数の名前である)。
この長さは、つぎの式の内側にある。
(- length (length kill-ring-yank-pointer)) |
この式では、length
は、関数の始めにあるlet
文にて キルリングの長さを設定した変数である (変数の名前をlength
ではなくlength-of-kill-ring
とすれば、 この関数がより明確になると考える読者がいるかもしれない。 しかし、関数のテキスト全体を見れば短いので、 ここでやっているように関数を小さな部分部分に分解しなければ、 変数をlength
と命名しても邪魔ではない)。
したがって、(- length (length kill-ring-yank-pointer))
は、 キルリングの長さとkill-ring-yank-pointer
のリストの長さの差を取る。
これが関数rotate-yank-pointer
にどのように適合するのかを、 つぎの状況で分析してみよう。 kill-ring
と同じでkill-ring-yank-pointer
は キルリングの第1要素を指し、 rotate-yank-pointer
を引数1で呼び出したとする。
この場合、変数length
はキルリングの長さであり、 kill-ring-yank-pointer
はキルリング全体を指しているので、 変数length
と式(length kill-ring-yank-pointer)
の値は同じである。 そのため、
(- length (length kill-ring-yank-pointer)) |
の値は0になる。 arg
は1なので、式全体では
(+ arg (- length (length kill-ring-yank-pointer))) |
の値は1になる。
したがって、nthcdr
の引数は、つぎの式の結果である。
(% 1 length) |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
%
%
"へのコメント(無し)
(% 1 length)
を理解するには%
を理解する必要がある。 (C-h f % RETとタイプして探した)その説明文によれば、 関数%
は、第1引数を第2引数で割ったときの余りを返す。 たとえば、5を2で割った余りは1である (2を2倍して余り1を加えると5になる)。
算術をほとんどしない人には、 小さな数を大きな数で割ることができて余りがあることに驚くかもしれない。 例では、5を2で割った。 逆に、2を5で割った結果はどうなるであろう? 小数を使えば、答えは、2/5、つまり、0.4である。 しかし、整数しか使えない場合には、結果は異なったものになる。 明らかに、5を0倍すればよいだろうが、余りはいくつだろう? 答えを探すには、子どものころから馴染み深い場合分けを考える。
これに対比させると、
などなどである。
したがって、このコードでは、length
の値は5なので、
(% 1 5) |
を評価した結果は1である (式の直後にカーソルを置いてC-x C-eとタイプして確認した。 もちろん、エコー領域には1と表示される)。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
rotate-yank-pointer
における%
の利用rotate-yank-pointer
における%
の利用"へのコメント(無し)
kill-ring-yank-pointer
がキルリングの先頭を指し、 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)) |
この動作を理解するのは簡単である。 キルリングの第1要素を指していたものが、kill-ring-yank-pointer
は 第2要素を指すように設定される。
明らかに、rotate-yank-pointer
に渡す引数が2の場合、 kill-ring-yank-pointer
には(nthcdr 2 kill-ring)
が設定される。 引数に他の値を指定すると変わる。
同様に、kill-ring-yank-pointer
がキルリングの第2要素を 指している状態では、その長さはキルリングの長さより1小さいので、 余りの計算は式(% (+ arg 1) length)
をもとに行われる。 つまり、rotate-yank-pointer
へ渡す引数が1ならば、 kill-ring-yank-pointer
は、キルリングの第2要素から第3要素に移動する。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
最後の疑問は、kill-ring-yank-pointer
がキルリングの最後の 要素を指しているとどうなるかである。 rotate-yank-pointer
を呼び出すと、 キルリングからは何も取り出せないのであろうか? 答えは否である。 これとは異なる有用なことが起こる。 kill-ring-yank-pointer
は、キルリングの先頭を指すように設定される。
これがどのように行われるかを、キルリングの長さを5、 rotate-yank-pointer
に渡す引数を1と仮定して、コードを見てみよう。 kill-ring-yank-pointer
がキルリングの最後の要素を指していると、 その長さは1である。 コードはつぎのとおりであった。
(% (+ arg (- length (length kill-ring-yank-pointer))) length) |
変数に数値を入れると、式はつぎのようになる。
(% (+ 1 (- 5 1)) 5) |
もっとも内側の式から外側に向かって評価する。 (- 5 1)
の値は4、(+ 1 4)
の合計は5、 5を5で割った余りは0である。 したがって、rotate-yank-pointer
が実行することは
(setq kill-ring-yank-pointer (nthcdr 0 kill-ring)) |
であり、kill-ring-yank-pointer
はキルリングの先頭を指すように設定される。
rotate-yank-pointer
を連続して呼び出すと、 キルリングの最後に達するまでは、 kill-ring-yank-pointer
はキルリングの要素を順番に指し、先頭に戻る。 リストには終わりがないかのように先頭に戻るので、 キルリングをリングとよぶのである (リングとは、終わりがないもの?)。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
yank
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)は挿入するものである。 関数に渡された引数がcons
ならば、ポイントとマークを交換して、 挿入したテキストの最後ではなく先頭にポイントを移動する。 このオプションは説明文で解説してある。 関数自体は、"*P"
を指定した対話的なものである。 これは、読み出し専用のバッファでは動作せず、 未処理の前置引数を関数に渡すことを意味する。
引数の渡し方 Pass the argument to rotate-yank-pointer
.負の引数を渡す Pass a negative argument.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
yank
の難しい部分は、rotate-yank-pointer
に渡す引数を決定する ための計算を理解することである。 幸い、一見したほど難しくはない。
一方あるいは両方のif
式を評価すると数になり、 その数がrotate-yank-pointer
に渡される引数となる。
注釈を付けると、コードはつぎのようになる。
(if (listp arg) ; 判定条件 0 ; 真の場合の動作 (if (eq arg '-) ; 偽の場合の動作、内側のif -1 ; 内側のifの真の場合の動作 (1- arg)))) ; 内側のifの偽の場合の動作 |
このコードは2つのif
式から成り、 一方の偽の場合の動作に他方が含まれている。
最初の、つまり、外側のif
式では、yank
に渡された引数が リストかどうかを調べる。 奇妙であるが、引数なしでyank
が呼ばれると、これは真になる。 省略できる引数に渡される値はnil
であり、 (listp nil)
を評価すると真を返すからである。 そこで、yank
に引数を渡さないと、yank
の中の rotate-yank-pointer
に渡す引数は0である。 つまり、予想どおりに、ポインタは移動されずに、kill-ring-yank-pointer
が 指す先頭要素が挿入される。 同様に、yank
への引数がC-uであると、これはリストとして読まれ、 この場合もrotate-yank-pointer
には0が渡される (C-uは、未処理の前置引数である(4)
となり、 これは1要素のリストである)。 同時に、関数のうしろの部分で、この引数はcons
であることがわかるので、 ポイントを挿入したテキストの始まりに、 マークを挿入したテキストの終わりに移動する (interactive
の引数P
は、省略可能な引数が略されていたり、 C-uだったりした場合にこれらの値を提供するためのものである)。
外側のif
式の真の場合の動作では、引数がなかったりC-uであった 場合の処理を行う。 偽の場合の動作では、それ以外の状況を処理する。 偽の場合の動作自身は、別のif
式である。
内側のif
式では、引数が負かどうかを調べる (METAと-キーを同時に押し下げるか、 ESCキーに続けて-キーを押すとこのようになる)。 この場合には、関数rotate-yank-pointer
には、 引数として-1が渡される。 そうするとkill-ring-yank-pointer
は逆向きに移動し、望んだ動作となる。
内側のif
の判定条件が偽(つまり、引数はマイナス記号ではない)であると、 式の偽の場合の動作が評価される。 これは式(1- arg)
である。 2つのif
式のために、引数が正の数か(マイナス記号だけではない)負の数の 場合にこの式が評価される。 (1- arg)
は、引数から1を引いた値を返す (1-
関数は、引数から1を引く)。 つまり、yank
への引数が1ならば、0に減らされ、 予想どおりに、 kill-ring-yank-pointer
が指す先頭要素が挿入される。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
最後に、剰余関数%
や関数nthcdr
に負の引数を与えると どうなるのであろうか?
試してみればわかる。 (% -1 5)
を評価すると、負の数が返される。 負の数でnthcdr
を呼び出すと、第1引数が0の場合と同じ値を返す。 つぎのコードを評価するとこのことがわかる。
`=>'のまえの式を評価すると`=>'のあとに 示した結果になる。 いつものように、コードの直後にカーソルを置いて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-ring-yank-pointer
は先頭に達するまで逆向きに巡回される。 そして、先頭で止まる。 リストの最後に達するとリストの先頭へ戻ってリングを形成するのとは異なり、 先頭で止まる。 これには意味がある。 なるべく最近に切り取ったテキストの断片を戻したいとしばしば思うだろうが、 30回もまえのキルコマンドからテキストを挿入したくはないであろう。 そこで、終わりに向かってはリングである必要があるが、 逆向きで先頭に戻った場合には巡回しない。
マイナス記号付きのどんな数をyank
に渡しても、-1と解釈される。 これはプログラムの記述を明らかに簡単にする。 キルリングの先頭へ向けて一度に1よりも大きく戻る必要はないので、 マイナス記号に続く数の大きさを調べるように関数を書くよりもよほど簡単である。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |
yank-pop
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'を指定したinteractive
があるので、 前置引数は処理してから関数に渡される。 コマンドはyankの直後でのみ使え、それ以外の場合にはエラーメッセージが送られる。 この検査には、すでに説明した変数last-command
を使っている (See 節 8.5 copy-region-as-kill
)。
let
節では、ポイントがマークのまえにあるかうしろにあるかに依存して 変数before
に真か偽を設定し、 ポイントとマークのあいだのリージョンを削除する。 これは直前のyankで挿入したリージョンであり、 この部分のテキストを置き換えるのである。 つぎに、kill-ring-yank-pointer
を巡回して、 直前に挿入したテキストを再度挿入しないようにする。 新たに挿入するテキストの先頭にマークを設定し、 kill-ring-yank-pointer
が指す先頭要素をポイントに挿入する。 これにより、テキストの終わりにポイントが置かれる。 直前のyankで挿入したテキストの先頭にポイントを置いてあった場合には、 ポイントとマークを入れ換えて、新たに挿入したテキストの先頭に 再度ポイントを置く。 以上である。
[ << ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] [?] |