2011-04-07 60 views
3

我正在學習通用lisp並試圖實現交換值函數來交換兩個變量的值。爲什麼以下不起作用?如何在lisp中執行值賦值問題

(defun swap-value (a b) 
      (setf tmp 0) 
      (progn 
       ((setf tmp a) 
       (setf a b) 
       (setf b tmp)))) 

錯誤信息:

in: LAMBDA NIL 
;  ((SETF TMP A) (SETF A B) (SETF B TMP)) 
; 
; caught ERROR: 
; illegal function call 

;  (SB-INT:NAMED-LAMBDA SWAP-VALUE 
;   (A B) 
+1

我不知道Common Lisp的足夠好,告訴你爲什麼你得到的錯誤,但我可以告訴你,這不會交換兩個變量的值,即使你修正這個錯誤,因爲參數'交換-value'是按值傳遞的,所以它們不會影響調用之外的綁定。 '(swap-value x y)'會傳入x和y的值,對'x'綁定的內容沒有影響。爲了達到你想要的效果,你將不得不寫一個宏。 – dfan 2011-04-07 01:31:49

+0

不錯的一點。讓我感到驚訝的是,我認爲setf是全球範圍內的分配者。我仍然堅持C程序員的思維。任何人都可以詳細說明Lisp的做價值分配的方法嗎? – lkahtz 2011-04-07 01:44:38

+0

'setf'是Lisp做值賦值的方式,但它不是引入全局變量的方法。你應該使用'defvar'或'defparameter'來引入全局的(和特殊的,也就是動態變量)。這是一個有點太複雜了一條註釋解釋,但檢查出實用的Common Lisp:http://www.gigamonkeys.com/book/variables.html – spacemanaki 2011-04-07 01:56:09

回答

3

dfan是正確的,這是不會交換的兩個值。

你雖然得到這個錯誤的原因是這樣的:

(progn 
    ((setf tmp a) 
    (setf a b) 
    (setf b tmp))) 

應該是這樣的:

(progn 
    (setf tmp a) 
    (setf a b) 
    (setf b tmp)) 

第一progn在體內一個s表達式,它的治療 作爲功​​能(setf tmp a)的應用。在Common Lisp中,I 認爲只有變量或lambda表單可以位於應用程序的函數 的位置。我可能錯在這裏的細節, ,但我知道在CL沒有在Scheme中的限制。這是 爲什麼這是一個非法的電話。

舉例來說,這是CL非法的,導致同樣的錯誤:

CL-USER> ((if (< 1 2) #'+ #'*) 2 3) 
; in: LAMBDA NIL 
;  ((IF (< 1 2) #'+ #'*) 2 3) 
; 
; caught ERROR: 
; illegal function call 
; 
; compilation unit finished 
; caught 1 ERROR condition 

你可以寫一個交換宏(警告:我是一個Lisp的小白,這 可能一個可怕的理由宏和寫得不好的一個!)

(defmacro swap (a b) 
    (let ((tmp (gensym))) 
    `(progn 
     (setf ,tmp ,a) 
     (setf ,a ,b) 
     (setf ,b ,tmp)))) 

都能跟得上!不要這樣做。正如Terje Norderhaug指出的那樣使用rotatef

+0

謝謝spacemanaki〜這是一個全面的答案。 – lkahtz 2011-04-07 02:23:01

12

您可以使用ROTATEF宏來交換兩個地方的值。更一般地說,ROTATEF將所有地方的內容旋轉到左邊。最左邊的 的內容被放在最右邊的位置。因此它可以與兩個以上的地方一起使用。

+0

謝謝。很有幫助。 – lkahtz 2011-04-07 02:21:05

+0

不是符號,變量。 – 2011-04-07 16:19:14

1

你不能使用setf建立一個詞彙變量tmp。您可以使用讓,如下:

(defun swap-value (a b) 
     (let ((tmp 0)) 
     (setf tmp a) 
     (setf a b) 
     (setf b tmp)) 
     (values a b)) 

,將你的希望。

+0

謝謝你。它看起來像 - 如果我在'progn'範圍之外使用(setf tmp 0),則tmp變量將變爲全局變量。我可以在repl下檢查它。但'a和'b是本地的。任何人都請糾正我,如果我錯了這個。使用let是正確的方式,確實如此。 – lkahtz 2011-04-07 02:55:53

+0

對符號外的符號使用setf將值綁定到符號確實會創建一個全局變量。某些Common Lisp實現可能會發出關於未聲明變量的警告。 – 2011-04-07 04:15:14

+0

在這種特殊情況下(取兩個值,以相反的順序返回它們作爲兩個返回值),它很可能是最好只用(defun定義交換值(AB)(值BA)),另外還要注意,無論是我的功能也沒有@wislin的功能實際上改變狀態。 – Vatine 2011-04-08 10:13:34

4

交換兩個特殊變量的函數(而不是宏)可以將變量符號作爲參數。也就是說,您引用了通話中的符號。下面是這樣的交換功能的實現:

(defvar *a* 1) 
(defvar *b* 2) 

(defun swap-values (sym1 sym2) 
    (let ((tmp (symbol-value sym1))) 
    (values 
    (set sym1 (symbol-value sym2)) 
    (set sym2 tmp)))) 

? (swap-values '*a* '*b*) 
2 
1 

? *a* 
2 

注意使用defvar定義全局/特殊變量,並在其名稱中按照慣例使用耳罩(星星)。所述symbol-value功能提供一個符號的值,而set分配一個值,以從評估其第一個參數所產生的符號。該values有使功能從兩個set語句返回兩個值。

+0

非常感謝Terje〜你現在的答案非常完整和徹底。 Lispers真是太好了〜努力學習成爲你的一個傢伙~~ – lkahtz 2011-04-07 14:16:10

+3

這不會詞法變量的工作。 – 2011-04-07 16:17:36