インタラクティブな引数指定。

日にちが空いてしまったので新しいエントリで。interactiveの使い方がようやく分かってきた。xyzzyでは、関数は普通に呼び出されるときと、コマンドとして呼び出されるときがある。interactiveは、関数がコマンドとして呼び出されたときに、どういう挙動をするかを示す関数だ。最低限、関数の内部にinteractiveがないと、コマンドとして呼び出すことができない。まあ、interactiveの説明はreferenceを見ればいいのだけど、解りづらい部分があったので自分でまとめてみる。


interactiveには、コマンドとして呼び出されたときの引数を決める役割がある。引数はリストで渡す。

(defun test (&optional (arg "default") (arg2 "default"))
  (interactive '("interactive"))
  (format t "~A:~A" arg arg2))
(test)
>default:default
>nil

(call-interactively 'test)
>interactive:default
>nil

こんな感じで、optional指定してあっても、interactiveで渡した場合は無視される。interactiveで値を渡さない場合は、optionalで指定されたものを使う。まあ、次のコードを簡単にしたものだと思えばいいか。

(defun test2 (&optional (arg "default"))
  (interactive)
  (if (interactive-p)
	  (setq arg "interactive"))
  (format t "~A" arg))

こういう感じなので、引数の値を定義するときにread-charやread-stringを使えば、引数をコマンド実行時に対話的に指定できるわけだ。また、実際、コマンド実行時には対話的に引数を指定したいということが良くあるので、interactiveは、それを簡単に記述できるようになっている。interactiveの引数として、リストではなく、文字列を指定すると、引数指定を簡略化した書式で書くことができる。

(defun test3 (arg)
  (interactive "*s文字列: "
	(format t "~A" arg)))

(defun test4 (&optional arg)
  (interactive)
  (if (interactive-p)
	  (if buffer-read-only
		  (error "このバッファは読み取り専用です。")
		(progn (setq arg (read-string "文字列: ")))))
  (format t "~A" arg))

test3とtest4は、大体同じような挙動をする。test4は引数を与えなくても動いちゃったりするけど。ともあれ、interactiveは上のように、コマンドとして実行するときの動作を記述しやすくしてくれるわけだ。

履歴の使い方

今回調べるまでは、interactiveでの引数指定は特別なものだと思っていたので、履歴もinteractiveで管理しているものだと思っていた。それで、インタラクティブな引数指定の履歴(未)。 - 象徴ヶ淵みたいなエントリを作ってみたのだが、ちょっと勘違いだったようである。


ミニバッファの履歴は、単に、ed::minibuffer-history-variable プロパティが設定されている変数を使えばいいらしい。

(setq foo nil)
(setf (get 'foo 'ed::minibuffer-history-variable) 'foo)
(read-string "なんか書いて: " :history 'foo)

こんな風にすればいい。こうやって設定した変数を、interactiveでも用いることができる、と。ちなみに、:historyのあとの0は引数の順番を示している。

(defun test5 (arg)
  (interactive "*sなんか書いて: " :history0 'foo)
  (format t "~A" arg))

さらに、この履歴をxyzzyが終了したときでも保存したい場合は、define-history-variableで変数名を指定してやる。すると、xyzzy側で内容を保存したり、読み出したりしてくれる。必要がなくなれば、unregister-history-variableで消せばよい。

で、どうやって使おうか。

結局、historyを使うには、プロパティを設定するだけで良いらしい。これが何で必要かは解らないけど。また、interactiveで用いる必要はないらしい。そういうことであれば、ヒストリを切り替えたりだとか、バッファごとに違うものを使うとかも出来そうだ。