common lisp defvarとdefparameterの違い

http://d.hatena.ne.jp/smeghead/20080207/lisp 関連 id:ibazaさんのコードでは、変数宣言にdefvarが使われていました。自分のコードは、defparameterで変数宣言してます。あまり、違いを気にしていなかったんですが、hyperspecを調べてみました。 defvarは値を設定しなくても良いという違いはありますが、注目した違いは、
  • defvar は、宣言しようとしていた変数名の値が既に存在していたら、再設定は行なわない。
  • defparameterは、宣言しようとしていた変数名の値が既に存在するしないに関わらず、設定を行なう。
です。
[1]> (defvar *var* 1)
*VAR*
[2]> (defvar *var* 2)
*VAR*
[3]> *var*
1
[4]> (defparameter *param* 1)
*PARAM*
[5]> (defparameter *param* 2)
*PARAM*
[6]> *param*
2
既に存在する変数宣言が書かれているファイルをloadした時などに動作の違いが出てくるってことのようです。でも、よく主観的な理由で使われるとも書かれていました。 何度もロードされる宣言かどうかによって、使い分けが必要になることがあると理解しました。それ以外の場合で、使うときは名前のイメージから使い分ければ良さそうです。(defparameterは、外部からそのパッケージの動作を変更するパラメータとして、defvarは、まさに変数として、という感じ?)

これらを踏まえての修正版

ですが、ついでに抽象化を進めてみました。探す文字列はトピックの問題では、”JOI” “IOI”という3文字の文字列2つに限定されていますが、defparameterで任意の文字列の文字列を任意の数だけ指定できるようにしました。このプログラム内部で使用する変数は、defvarで宣言しました。

 

(defvar *hit-words* '())
(defparameter *words-to-search* '("JOI" "IN" "JOIN"))
(defun make-checker (word)
  (let ((cursor 0)
        (len (length word)))
    #'(lambda (c)
        (and (eq (aref word cursor) c)
             (incf cursor)
             (if (eq cursor len)
               (prog1 nil (push word *hit-words*))
               t)))))
(defun check-words (words input-stream)
  (loop for c = (read-char input-stream)
        with checkers = nil
        until (eq c #\Newline)
        do (mapcar #'(lambda (word)
                       (push (make-checker word) checkers))
                   words)
        (setq checkers (remove nil
                               (mapcar
                                 #'(lambda (fn)
                                     (if (funcall fn c) fn))
                                 checkers)))
        finally (return *hit-words*)))
(defun print-count-words (words &optional (input-stream *standard-input*))
  (let ((hit-words (check-words words input-stream)))
    (format t "~{~d~%~}"
            (mapcar #'(lambda (word)
                        (count word hit-words :test 'equal))
                    words))))
(print-count-words *words-to-search*)

実行結果

$ echo "JOIN IN" | clisp joioi.lisp
1
2
1

JOI が 1個、 IN が 2個、 JOIN が 1個なので、ちゃんと動いているようです。

defvar で値を省略して宣言できるけど、値がnilになるという訳でもないので、省略して宣言した直後に値を参照したり設定しようとするとエラーになるんですね。これだと省略できても嬉しくないな。

[1]> (defvar *var*)
*VAR*
[2]> *var*
*** - EVAL: variable *VAR* has no value
The following restarts are available:
USE-VALUE      :R1      You may input a value to be used instead of *VAR*.
STORE-VALUE    :R2      You may input a new value for *VAR*.
ABORT          :R3      ABORT

2件のコメント

  • こんな違いがあったとは知りませんでした。
    今までなんとなくdefvarを使っていたのでdefparameterとの違いも特に調べることなくスルーしてしまいました。
    こういう怠け心がバグを発生させる一番の原因なのかもしれません。

  • 私も今日知ったので^^
    defvarの利点は、「短かい」というのもありますねw。でもちゃんと使い分けるとコードの読み易さには貢献できるかもしれないです。

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です


reCaptcha の認証期間が終了しました。ページを再読み込みしてください。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください