データ解析作業のためのlisp
データ解析の一般的な流れは知らないが、xyzzyのlisp-interaction-modeで次のように作業している。
- データを読み込む
- データを操作して特徴量などを計算し、変数に記録する
- 計算した結果を比較したりプロットしたりする
時間のかかる計算でも、結果を保存しておけば再利用が楽である。
ところが、エディタ上で適当に書いた関数で計算させているため、バグがあってエディタがクラッシュしてしまう場合がある。
そうすると、今まで計算していた結果が失われて、書いた関数も消えて悲しみに包まれる。
そこで、危険を回避するための方法をまとめてみる(解析の方法ではない、念のため)。
実行前に保存する
大体、時間のかかる計算をさせるためにはコンパイルしてやる必要がある。
書いた関数をその場でコンパイルしても良いのだが、次回読み込んだときに関数を1つずつコンパイルするのは面倒なので、関数を実行する度にファイル単位でコンパイルしてやる。
(defun save-compile-and-load (&optional (buffer (selected-buffer))) (interactive) (let ((file (get-buffer-file-name))) (or (and file (buffer-modified-p) (save-buffer) (compile-file file) (load (compile-file-pathname file))) (message "ないよ"))))
こういう関数を用意して、適当にM-xで呼び出すかキーに割り当てれば良い。
計算結果を保存する
上のようにloadすると、計算を実行する関数を同じファイルに書いている場合は同時に読み込れてしまう。
実際に行わせたい処理はこうだ。
- 計算結果が変数に保存されている場合は計算しない。
- 計算結果が変数に保存されていない場合は計算する。
- 計算を実行したら結果をファイルに書き出す。
- 結果がファイルに保存されている場合は、そこから読み込む。
こうすれば、わざわざxyzzyを起動するたびに計算せずに済むし、コードをloadし直した場合も読み込まなくて良い。
計算結果を直したい場合は、個別に計算させるか、変数をmakunboundして結果を保存したファイルを消せば良い。
ということで、上の処理を自動的に行う処理を考える。
(defmacro history-defvar (name &body body) `(progn (defvar ,name) (unless (or (boundp ,name) (load-stored-value ',name)) (setq ,name ,@body) (store-value ',name)))) (defun store-value (name) (with-open-file (op (value-store-file name) :direction :output :if-does-not-exist :create :if-exists :overwrite) (format op "~a" (symbol-value name)) (symbol-value name))) (defun load-stored-value (name) (let ((file (value-store-file name))) (if (file-exist-p file) (with-open-file (op file) (setf (symbol-value name) (read op)) t)))) (defun value-store-file (file) (merge-pathnames (string-trim "*" (symbol-name name)) *store-path*)) (defvar *store-path* nil)
このように定義して
(history-defvar *hoge* (make-list 10))
と書いたりすると、上記の通りに動いてくれる。ファイル名は変数名から耳を外して適当に付けるが、/や?などが入っていても気にしないので、使うときに注意する。また、一度保存してから定義を直しても自動では上書きしないので、更新するときは気をつける。
細かい挙動は必要に応じて書き換えると良いだろう。
なお、xyzzyはヒストリ変数に対応しているが、データを扱わない場合にも読み込んでくれてしまうので、この場合は別ファイルに保存した方が良い。