方法が分からないlispの話

最近色々試しているときに時々やりたいけどやり方がわからずに出来ない話とか

  1. 同じ値を二つの変数名から同様に扱う(aliasみたいに)
  2. 引数で貰った変数の値を変更する

2番目はこういう感じで何とかしたいけど出来ない

(defvar *test* nil)

(defun hoge(arg)
  (setq arg "hoge"))

(hoge *test*)
=> t
*test*
=>nil
;ここで"hoge"と出て欲しい

どちらもポインタみたいな変数の場所情報さえ使えれば簡単なんだけど、lispだとどうしたら良いのかよく分からないけど、帰ってきた値を入れ込んで変数の値を変えるのかな。そもそも、こういう変数の渡し方に問題があるのかもしれない。

iread-stringに:historyの引数をread-stringに倣って取り付けようと思ったりしてるんだけど、引数で貰ってしまうとと本体の方へ履歴が加えれないとか。:historyじゃない方法でやれば出来そうな気はするけど、なんか関数の扱いに統一感がないからできたらやりたくない感じ。履歴の変数を戻り値で貰えば簡単そうには思ってみたり。

■名前を元に呼び出してみるとか。
アレコレ考えてたらこうすれば出来るのに気づいてみた。そのまま、名前を場所代わりにすればいい感じ。

(setq *test* nil)

(defun hoge(arg)
  (set (car (find-all-symbols arg)) "hoge"))

(defun tester()
  (interactive)
  (hoge "*test*"))

*test*
=>"hoge"

ただこれだと引数に文字列で変数の名前を入れるという何だか風変わりな構図ではあるけど。取り合えずコレで試してみようかと思ってたら、なんか違うのに気づいた。

■変数の中に変数?

(setq *fuga* nil)
(setq *fuga* (car (find-all-symbols "*test*")))

こうしておいて*fuga*を評価したらアレ?

*fuga*
=>*test*

なんか中に変数普通に入るじゃんとか。

;これはつまり

(setq *fuga* nil)
(setq *fuga* '*test*)
*fuga*
=>*test*

;こういうことか

今まで全然気づいてなかった。が、良く考えたら関数が入るんだから変数が入っても不思議じゃないのかもしれない。いや、少し不思議だけど。

■なんか出来た
自分でゴチャゴチャ考えてたら何となく解決したので適当にくっつけてみた。

;; var

;core var
(defvar *iread-string-prompt* nil)
(defvar *iread-current-string* nil)
(defvar *iread-string-this-command* nil)
(defvar *iread-string-command-char* nil)
(defvar *iread-string-self-insert* nil)

;history var 履歴用、今回は消したら動かない。と思う。
(defvar *iread-string-last-history* nil)
(defvar *iread-string-history-index* nil)
(defvar *iread-string-history-last-string* nil)
(defvar *iread-string-history* nil)

(defvar *iread-string-default-history* nil)
(register-history-variable '*iread-string-default-history*)

;;keymap
(defvar *iread-string-map* nil)
(unless *iread-string-map*
  (setq *iread-string-map* (make-sparse-keymap))
  (define-key *iread-string-map* #\C-g 'quit)
  (define-key *iread-string-map* #\C-h 'iread-delete-char)
  (define-key *iread-string-map* #\RET 'iread-string-exit)
  (define-key *iread-string-map* #\Up 'iread-string-history-backward)
  )

;; core
(defun iread-string (&key (prompt "iread:")
						  (default "")
						  (keymap *iread-string-map*)
						  (func (symbol-function 'key-test))
						  (history '*iread-string-default-history*)
						  )
  (interactive)
  (let ((*iread-string-prompt* prompt)
	(*iread-current-string* default)
	(*iread-string-map* keymap)
	(*iread-string-no-clear* nil)
	(*iread-string-history* (eval history))
	(*iread-string-history-index* -1)
	(*iread-string-last-history* nil)
	(*iread-string-history-last-string* nil)
	(*iread-string-this-command* nil)
	(*iread-string-last-command* nil)
	(*iread-string-status* nil)
	(*iread-string-self-insert* func)
		)
    (setq *last-iread-string-regexp-p* nil)
    (unwind-protect
	(catch 'iread-exit
	  (loop
	    (refresh-screen)
	    (minibuffer-prompt "~a~a" *iread-string-prompt* *iread-current-string*)
	    (let ((*iread-string-command-char* (read-char *keyboard*)))
	      (setq *iread-string-this-command* (lookup-keymap *iread-string-map*
							  *iread-string-command-char* t))
		  (if (graphic-char-p *iread-string-command-char*)
			  (setq *iread-current-string*
					(concat *iread-current-string*
							(string *iread-string-command-char*))))
	      (if *iread-string-this-command*
			  (funcall *iread-string-this-command*)
			(funcall *iread-string-self-insert* *iread-current-string*))
	      (setq *iread-string-last-command* *iread-string-this-command*)
		)))
	  (unless *iread-string-no-clear*
		(add-history *iread-current-string* history))
	  *iread-current-string*)))

(defun key-test(str)
  (message "key ~C string ~A" *iread-string-command-char* str))


;; iread-string basic function
(defun iread-string-exit()
  "exit iread string"
  (unless (string= *iread-current-string* "")
	(setq *last-iread-string* *iread-current-string*))
  (throw 'iread-exit t))

(defun iread-delete-char ()
  "delete char iread string"
  (let ((l (length *iread-current-string*)))
	(if (zerop l)
	    (ding)
	  (setq *iread-current-string* (subseq *iread-current-string* 0 (- l 1))))
	(funcall *iread-string-self-insert* *iread-string-command-char*)
	(setq *iread-this-command* 'iread-delete-char)
	))


(defun iread-string-history-backward ()
  "iread string history backward"
  (if (eq *iread-string-last-history* *iread-current-string*)
      (setq *iread-string-history-index*
	    (min (max 0 *iread-string-history-index*)
		 (1- (length *iread-string-history*))))
    (progn
      (setq *iread-string-history-index* -1)
      (setq *iread-string-history-last-string* *iread-current-string*)))
  (let ((l (length *iread-string-history-last-string*)))
    (loop
      (if (eq *iread-string-this-command* 'iread-string-history-backward)
	  (incf *iread-string-history-index*)
	(decf *iread-string-history-index*))
      (let ((s (unless (minusp *iread-string-history-index*)
		 (nth *iread-string-history-index* *iread-string-history*))))
	(unless s
	  (ding)
	  (return))
	(let ((match (if *case-fold-search*
			 (string-not-equal s *iread-string-history-last-string*)
		       (string/= s *iread-string-history-last-string*))))
	  (when (or (null match)
		    (= match l))
	    (setq *iread-string-last-history* s)
	    (setq *iread-current-string* s)
	    (return))))))
  (setq *iread-string-this-command* 'iread-string-history-forward))

これはこの前書いたiread-stringに適当に履歴を付けてみたものだけど。実際には下のように使う。

;今回つけた履歴用の変数を作る
(defvar *test* (list "fuga" "hoge" "oo"))
(register-history-variable '*test*) 
;この変数をregister-history-variableしてしまうxyzzy終了しても消えないので
;後でunregister-history-variableする必要あり。

;適当にキーを押す毎に動く動作を決めておいて。
(defun scan(c)
  (scan-buffer *iread-current-string*))

;肝心のiread-stringを呼び出し
(defun tester()
  (interactive)
  (iread-string :func 'scan :history '*test*)
  )

 これで、適当にtesterを呼び出して↑を押すと履歴が出る。:historyに入れる履歴用変数を変えるとその変えたやつの履歴が出るはず。多分。まだ今作ったばかりなので、詳しくは不明。iread-string自体は前のより、少し色々問題のあったのを直したりはしてるけど基本は前と同じもの。まだ↑しか対応してないので↓で戻ったりは出来ない。
 無理やり履歴を入れてしまったせいで前のより、少し汎用性は下がってそう。ここまでやるなら思い切って履歴は別の好きに出来る履歴用のを作ってiread-stringと区別してみた方が良かったかもしれない。そもそも他のlispの履歴部分が殆どビルトイン関数とかで処理しているので、↑で戻ってとかするのをインクリメンタルに対応できるような汎用性の高い履歴の関数自体があんまりない感じだし、そういうのを意識して作った方がいいのかもしれない。今の奴はisearchの変数とかを適当に変えてiread-stringから呼べるようにしているだけ。

■あんまり関係ないけどsetqのqについて
 今日はなんかsetqの"q"の部分に開眼した気分。ああ、そういうことだったのかアレとか、今頃気づいた。あと、調べてる途中で気づいたgetとかいうのがマタ凄い。変数にプロパティが付けれるなんて今まで知らなかったなぁ。これは凄いような気がする。lispの短い関数はいつも危険な臭いがする。

getはここで見た。
シンボルとパッケージ
http://www.fireproject.jp/feature/common-lisp/details/symbol-package.html#2
このfireprojectと言う所は凄い役に立つ。"lisp一夜漬け"を見た後は何かとここにお世話になってる。