実用的なLISP は(defvar *db* nil)
グローバル変数を設定します。同じ目的でsetq
を使用しても問題ありませんか?
defvar
とsetq
を使用する利点/欠点は何ですか?
変数を導入するにはいくつかの方法があります。
[〜#〜] defvar [〜#〜] および [〜#〜] defparameter [〜#〜] 導入global動的変数。 DEFVAR
は、すでに定義されていない限り、オプションでそれをある値に設定します。 DEFPARAMETER
は常に指定された値に設定します。 [〜#〜] setq [〜#〜] は変数を導入しません。
(defparameter *number-of-processes* 10)
(defvar *world* (make-world)) ; the world is made only once.
DEFVAR
、x
、y
、stream
、...のような名前のlimit
変数は絶対に使用したくないことに注意してください。これらの変数は特別なものとして宣言され、元に戻すのが難しいためです。特別な宣言はグローバルであり、変数をさらに使用する場合はすべて動的バインディングを使用します。
悪い:
(defvar x 10) ; global special variable X, naming convention violated
(defvar y 20) ; global special variable Y, naming convention violated
(defun foo ()
(+ x y)) ; refers to special variables X and y
(defun bar (x y) ; OOPS!! X and Y are special variables
; even though they are parameters of a function!
(+ (foo) x y))
(bar 5 7) ; -> 24
より良い:常に特殊変数に名前に*
を付けてください!
(defvar *x* 10) ; global special variable *X*
(defvar *y* 20) ; global special variable *Y*
(defun foo ()
(+ *x* *y*)) ; refers to special variables X and y
(defun bar (x y) ; Yep! X and Y are lexical variables
(+ (foo) x y))
(bar 5 7) ; -> 42
ローカル変数は [〜#〜] defun [〜#〜] 、 [〜#〜] lambda [〜#〜] で導入されます=、 [〜#〜] let [〜#〜] 、 MULTIPLE-VALUE-BIND およびその他多数
(defun foo (i-am-a-local-variable)
(print i-am-a-local-variable))
(let ((i-am-also-a-local-variable 'hehe))
(print i-am-also-a-local-variable))
現在、上記の2つの形式のローカル変数は、宣言されていない限り、レキシカルです[〜#〜] special [〜#〜]。次に、それらは動的変数になります。
次に、変数を新しい値に設定するいくつかの形式もあります。[〜#〜] set [〜#〜] =、 [〜#〜] setq [〜#〜] 、 [〜#〜] setf [〜#〜] など。 SETQ
とSETF
は、字句変数と特殊(動的)変数の両方を設定できます。
移植可能なコードでは、すでに宣言されている変数を設定する必要があります。宣言されていない変数を設定することの正確な効果は、規格では定義されていません。
したがって、Common LISP実装が何を行うかがわかっている場合は、
(setq world (make-new-world))
トップレベルのRead-Eval-Print-Loop内。ただし、効果は移植できないため、コードでは使用しないでください。通常、SETQ
は変数を設定します。しかし、一部の実装では、変数[〜#〜] special [〜#〜]を宣言していない場合があります(CMU Common LISPはデフォルトでそれを行います)。それはたいていの場合、望んでいることではありません。あなたが何をすべきか知っているならカジュアルな使用のためにそれを使用してください。
こっちも一緒:
(defun make-shiny-new-world ()
(setq world (make-world 'shiny)))
まず、そのような変数は、グローバルな特殊変数であることを明確にするために、*world*
(周囲の*
文字を含む)として記述する必要があります。第二に、それは以前にDEFVAR
またはDEFPARAMETER
で宣言されているはずです。
一般的なLISPコンパイラは、上記の変数が宣言されていないことを報告します。 Common LISPにはグローバルレキシカル変数が存在しないため、コンパイラーは動的ルックアップ用のコードを生成する必要があります。次に、一部のコンパイラは、大丈夫、これは動的ルックアップであると想定します。それをspecialであると宣言しましょう。
defvar
は動的変数を導入し、setq
は動的変数またはレキシカル変数に値を割り当てるために使用されます。動的変数の値は、関数を呼び出す環境で検索されますが、字句変数の値は、関数が定義された環境で検索されます。次の例は、違いを明確にします。
;; dynamic variable sample
> (defvar *x* 100)
*X*
> (defun fx () *x*)
FX
> (fx)
100
> (let ((*x* 500)) (fx)) ;; gets the value of *x* from the dynamic scope.
500
> (fx) ;; *x* now refers to the global binding.
100
;; example of using a lexical variable
> (let ((y 200))
(let ((fy (lambda () (format t "~a~%" y))))
(funcall fy) ;; => 200
(let ((y 500))
(funcall fy) ;; => 200, the value of lexically bound y
(setq y 500) ;; => y in the current environment is modified
(funcall fy)) ;; => 200, the value of lexically bound y, which was
;; unaffected by setq
(setq y 500) => ;; value of the original y is modified.
(funcall fy))) ;; => 500, the new value of y in fy's defining environment.
動的変数は、デフォルト値を渡すのに役立ちます。たとえば、動的変数*out*
を標準出力にバインドして、すべてのio関数のデフォルト出力にすることができます。この動作をオーバーライドするには、ローカルバインディングを導入するだけです。
> (defun my-print (s)
(format *out* "~a~%" s))
MY-PRINT
> (my-print "hello")
hello
> (let ((*out* some-stream))
(my-print " cruel ")) ;; goes to some-stream
> (my-print " world.")
world
字句変数の一般的な用途は、状態を使用してオブジェクトをエミュレートするためのクロージャの定義です。最初の例では、y
のバインディング環境の変数fy
が、その関数のプライベート状態になります。
defvar
は、まだ割り当てられていない場合にのみ、変数に値を割り当てます。したがって、次の*x*
の再定義は、元のバインディングを変更しません。
> (defvar *x* 400)
*X*
> *x*
100
setq
を使用して、*x*
に新しい値を割り当てることができます。
> (setq *x* 400)
400
> *x*
400
> (fx)
400
> (let ((*x* 500)) (fx)) ;; setq changed the binding of *x*, but
;; its dynamic property still remains.
500
> (fx)
400
[〜#〜] defvar [〜#〜] は新しい変数を確立します。 [〜#〜] setq [〜#〜] は変数に割り当てます。
私が使用したほとんどのLISP実装では、まだ存在しない変数にSETQを実行すると警告が表示されます。
defvar
とdefparameter
はどちらもグローバル変数を導入します。ケンが指摘するように、setq
は変数に割り当てます。
さらに、defvar
は以前にdefvar
- edされたものを上書きしません。 Seibelはこの本の後半(第6章)で次のように述べています。「実際には、DEFVARを使用して、変数を使用するソースコードに変更を加えても保持したいデータを含む変数を定義する必要があります。」
http://www.gigamonkeys.com/book/variables.html
たとえば、グローバル*db*
は、Simple Databaseの章のデータベースの場合:
(defvar *db* nil)
...そして、REPL-追加、削除など)で遊んでいますが、そのdefvarフォームを含むソースファイルに変更を加え、そのファイルを再ロードしても、一掃する *db*
およびあなたが加えた可能性のあるすべての変更... setq
もdefparameter
もそうだと思います。もっと経験豊富なLisperが間違っていた場合は修正してください。