web-dev-qa-db-ja.com

LETとSETQの違いは?

私はGCLを使用してUbuntuでプログラミングしています。さまざまなソースからのCommonLISPに関するドキュメントから、letlocal変数を作成し、setqexisting変数の値を設定することを理解しています。 。以下の場合、2つの変数を作成し、それらの値を合計する必要があります。

setqの使用

(defun add_using_setq ()
  (setq a 3)  ; a never existed before , but still I'm able to assign value, what is its scope?
  (setq b 4)  ; b never existed before, but still I'm able to assign value, what is its scope?
  (+ a b))

letの使用

(defun add_using_let ( )
  (let ((x 3) (y 4)) ; creating variables x and y
     (+ x y)))

どちらの場合も、私は同じ結果を達成しているようです。ここでsetqletを使用する違いは何ですか? setqを使用する必要があるすべての場所で(構文的に簡単なため)letを使用できないのはなぜですか?

24
Vijay Rajanna

setqassigns値を変数に割り当てますが、letは新しい変数/バインディングを導入します。例:で何が起こるか見てください

(let ((x 3))
  (print x)      ; a
  (let ((x 89))
    (print x)    ; b
    (setq x 73)  
    (print x))   ; c
  (print x))     ; d


3   ; a
89  ; b
73  ; c
3   ; d

外側のletはローカル変数xを作成し、内側のletは内側の変数をシャドウイングする別のローカル変数を作成します。 letを使用して変数をシャドウイングしても、シャドウイングされた変数の値には影響しないことに注意してください。行xdは外部のxによって導入されたletであり、その値は変更されていません。 setqは、呼び出される変数にのみ影響します。この例は、ローカル変数で使用されるsetqを示していますが、特別な変数(つまり、動的にスコープされ、通常はdefparameterまたはdefvarで定義される)で使用することもできます。

CL-USER> (defparameter *foo* 34)
*FOO*
CL-USER> (setq *foo* 93)
93
CL-USER> *foo*
93

setqは(移植性が高い)create変数ではないのに対し、letdefvardefparameter、&cであることに注意してください。行う。 (まだ)変数ではない引数で呼び出されたときのsetqの動作は定義されておらず、何をするかを決定するのは実装次第です。たとえば、SBCLは大声で不平を言います:

CL-USER> (setq new-x 89)

; in: SETQ NEW-X
;     (SETQ NEW-X 89)
; 
; caught WARNING:
;   undefined variable: NEW-X
; 
; compilation unit finished
;   Undefined variable:
;     NEW-X
;   caught 1 WARNING condition
89

もちろん、これらの概念をよりよく理解するための最良の方法は、より多くのLISPコード(時間とともに付属)を読み書きし、HyperSpecのエントリを読み取り、相互参照、特に用語集のエントリに従うことです。たとえば、HyperSpecのsetqおよびletの簡単な説明は次のとおりです。

  • SETQ

    変数 に値を割り当てます。

  • LET

    letおよびlet *は、新しい変数 bindings を作成し、これらのバインディングを使用する一連のフォームを実行します。

変数とバインディングについてもっと読みたいと思うかもしれません。 letおよびlet*には、動的変数とspecial宣言に関する特別な動作もあります(ただし、しばらくの間はそれについて知る必要はないでしょう)。場合によっては(おそらく知る必要はありません)。しばらくの間)変数が実際には変数ではない場合、setqは実際にはsetfと同等です。 HyperSpecに詳細があります。

Stack Overflowには、まったく重複していない質問がいくつかありますが、それでも、CommonLISPで使用できるさまざまな変数定義および代入演算子の使用法を理解するのに役立つ場合があります。

31
Joshua Taylor

同じスコープ内の他の関数で値を使用できるようにしたいというまれな場合を除いて、ほとんどの場合、関数定義内で変数をバインドする方法にする必要があります。

私はemacsLISPマニュアルの説明が好きです:

letは、LISPインタープリターが変数を関数の一部ではない同じ名前の変数と混同しないように、シンボルを値にアタッチまたはバインドするために使用されます。

特別なフォームが必要な理由を理解するために、あなたが一般的にthe house', as in the sentence, “The house needs painting.” If you are visiting a friend and your Host refers tothe house 'と呼ぶ家を所有している状況を考えてみましょう。彼はあなたの家ではなく、彼の家を参照している可能性があります。別の家。

あなたの友人が彼の家について言及していて、彼があなたの家について言及していると思うなら、あなたは混乱しているかもしれません。ある関数内で使用される変数が別の関数内で使用される変数と同じ名前であり、2つが同じ値を参照することを意図していない場合、LISPでも同じことが起こる可能性があります。 let特殊形式は、この種の混乱を防ぎます。

- http://www.gnu.org/software/emacs/manual/html_node/eintr/let.html

2
al.stevens

(setq x y)は新しい値yを記号xで指定された変数に割り当て、オプションで新しいパッケージレベルの変数を定義します 1。これは、add_using_setqを呼び出した後、現在のパッケージに2つの新しいパッケージレベルの変数があることを意味します。

(add_using_setq)
(format t "~&~s, ~s" a b)

3 4を出力します-望ましい結果はありそうにありません。

対照的に、letを使用する場合、関数の期間中、記号で指定された変数にのみ新しい値を割り当てるため、このコードはエラーになります。

(add_using_let)
(format t "~&~s, ~s" a b)

letは、次のコードと同等であると考えてください。

(defun add-using-lambda ()
  (funcall (lambda (a b) (+ a b)) 3 4))

余談ですが、他のプログラマーが書いたコードを調べて、名前を付けたりフォーマットしたりする方法を理解したいと思うでしょう。伝統的であることに加えて、それはあなたが本当に失いたくないいくつかの活版印刷の特性も持っています。

1この動作は非標準ですが、これは多くの一般的な実装で発生することです。それがかなり予測可能であるにもかかわらず、それは他の理由で悪い習慣と見なされます。ほとんどすべて同じ懸念が、グローバル変数の使用を思いとどまらせるでしょう。

1
user797257
  • SETQ

lISPがまだ実行されている限り、シンボルの値をスコープから外すことができます。 (シンボルに値を割り当てます)

  • LET

lISPがフォームの評価を終了した後は、LETで定義されたシンボルの値を取得できません。 (値をシンボルにバインドし、シンボルへの新しいバインドを作成します)

以下の例を検討してください。

;; with setq
CL-USER> (setq a 10)
CL-USER> a
10

;; with let
CL-USER> (let ((b 20))
       (print b))
CL-USER> 20
CL-USER> b ; it will fail
; Evaluation aborted on #<UNBOUND-VARIABLE B {1003AC1563}>.
CL-USER>
0
azzamsa