簡體   English   中英

Emacs Lisp 可以將 lambda 形式分配給像 Scheme 這樣的變量嗎?

[英]Can Emacs Lisp assign a lambda form to a variable like Scheme?

在調查 Emacs Lisp 的符號單元格時,我發現對於一個示例函數,如

(defun a (&rest x)
    x)

我可以調用(symbol-function 'a) ,它返回(lambda (&rest x) x) 如果我願意,我可以使用它

> ((lambda (&rest x) x) 1 2 3 4 5)
(1 2 3 4 5)

它與上面的原始函數具有相同的功能。 現在,這讓我想起了 Scheme,其中 lambda 表達式是函數的主體,並通過 Scheme 的通用define分配給變量名稱。 例如

(define atom?
    (lambda (x)
        (and (not (pair? x)) (not (null? x)))))

簡單地將 lambda 表達式分配給atom? - 現在atom? 是一個函數。 那么elisp 可以這樣做嗎,即,將一個lambda 表達式分配給一個符號,然后將其用作函數? 我試過了

(setq new-a (lambda (&rest x) x))

如果我嘗試將它用作函數,它會給出(void-function new-a) 有沒有辦法在這個問題上模仿Scheme世界? 好像一定有​​辦法。 如果我們不能把這個 lambda 表達式轉換成一個函數,那么為什么a的函數單元格會包含(lambda (&rest x) x)呢?

scheme 和 emacs lisp(以及大多數其他 lisp)之間的一個重要區別是,scheme 具有單個命名空間,而 emacs lisp 具有用於函數和變量的單獨命名空間。 被求值的列表形式中的第一個位置命名一個函數,並在函數命名空間中查找該名稱。 在方案中,所有名稱都位於同一個空間中,綁定到名稱的值會在出現的任何位置進行查找和使用。

這意味着在 emacs lisp 中你可以這樣:

(defun f (x) (+ x x))
(setq f 2)
(f f) ;=> 4

這在方案中是不可能的,這里只有一個f ,如果你設置它的值,它會從(比如)一個函數變成一個數字。

在 emacs lisp 中有不同的處理方法。

一種是使用諸如funcallapply的函數,它們接受一個函數和一些參數並將函數應用於參數,如下所示:

(setq f (lambda (x) (+ x x)))
(funcall f 2) ;=> 4

另一種方法是操縱函數名稱f含義。 有一個名為fset的函數,它允許您將函數附加到名稱(在函數命名空間中):

(fset 'f (lambda (x) (+ x x x)))
(f 2) ;=> 6

請注意, fset對名稱(又名符號)起作用,因此名稱f需要被引用,否則它將被讀取為變量的值。 這就是為什么變量的函數被稱為setq ,“q”代表“quoted”,所以setq實際上是一個特殊的函數,它引用了它的第一個參數,這樣程序員就不必這樣做了。 有一個稱為set的等效普通函數,它不進行任何引用,如下所示:

(setq x 1)  ; x is 1
(set 'x 2)  ; x is 2
(setq x 'x) ; x is the symbol x
(set x 3)   ; x is now 3

最后一種形式可能看起來令人困惑,但由於set是一種正常形式,它將查找變量x的值,該值是符號x ,然后命名將要更改的變量(即x )。 因此set一個優點是可以設置您不知道名稱而是通勤的變量。

這是另一個答案的附錄。 另一個答案解釋了 lisp-1s(具有用於函數和變量綁定的單個命名空間的 lisp)和 lisp-2s(具有用於函數綁定的單獨命名空間的 lisp)之間的區別。

我想解釋為什么 lisp-2 可以讓事情變得更好,尤其是為什么它在歷史上是這樣的。

首先讓我們考慮一些 Scheme 代碼:

(define (foo x)
  (let ([car (car x)])
    ... in here (car ...) is probably not going to get the car
    (bar car)))


(define (bar thing)
  ... but in here, car is what you expect ...)

因此,在foo我已將car綁定到參數的汽車。 這在 Scheme 中可能是糟糕的風格,這意味着,在該綁定的主體中,當用作函數時, car可能不會執行您期望的操作。 但這個問題只有結合的詞法范圍內的事項car :它並不重要內bar的實例。

現在,在 Common Lisp 中,我可以編寫等效的代碼:

(defun foo (x)
  (let ((car (car x)))
    ... (car ...) is fine in here ...
    (bar car)))

(defun bar (thing)
  ... and here ...)

所以這可能更好一點,也許:在car的綁定體中,將car作為函數仍然可以使用,而且編譯器確實可以做出非常強烈的假設,即car是語言定義的函數,並且 CL 有措辭在確保這總是正確的標准中。

這意味着,在風格上,在 CL 中,這樣的事情可能沒問題。 特別是我經常做這樣的事情:

(defmethod manipulate-thing ((thing cons))
  (destructuring-bind (car . cdr) thing
    ...use car & cdr...))

而且我認為這很好:在 Scheme 中,等效的東西會很可怕。

所以這就是為什么 lisp-2 非常方便的原因之一。 但是有一個更強大的一個,其並不適用於CL適用於elisp的。

考慮一下,在 elisp 中,這段代碼:

(defun foo (x)
  (let ((car (car x))
        (cdr (cdr x)))
    (bar car cdr)))

(defun bar (thing-1 thing-2)
  ...)

現在有一個關於 elisp 的重要信息:默認情況下它是動態范圍的。 這意味着,當從foo調用bar時, carcar的綁定在bar中可見

例如,如果我將bar重新定義為:

(defun bar (thing-1 thing-2)
  (cons cdr thing-1))

然后:

ELISP> (foo '(1 . 2))
(2 . 1)

所以,現在,想想如果 elisp 是一個 lisp-1 會發生什么:foo調用的任何函數都會發現(car x)沒有做它期望的事情 這是一場災難:這意味着如果我將一個函數的名稱——任何函數,包括我可能不知道存在的函數——綁定為一個變量,那么該綁定的動態范圍內的任何代碼都不會做它應該做的事情。

因此,對於具有動態范圍的 Lisp,就像 elisp 歷史上默認情況下一直存在並且仍然存在一樣,成為 lisp-1 是一場災難。 嗯,從歷史上看,很多 lisp 實現確實具有動態范圍(至少在解釋代碼中:編譯代碼具有不同的范圍規則是很常見的,並且范圍規則通常通常有些不連貫)。 所以對於這些實現,成為 lisp-2 是一個非常重要的優勢。 而且,當然,一旦存在大量假定 lisp-2-ness 的代碼,對於以兼容性為目標的語言(例如 CL)來說,保持 lisp-2s 就容易多了,即使在詞法范圍語言中具有優勢不太清楚。


注意:很久以前,我使用過 lisp,它既是動態范圍的(至少在解釋器中?)又是 lisp-1。 並且我至少有一次非常糟糕的經歷(我認為需要硬重置一台已經變得緊張的多用戶機器,因為它分頁太多,這讓我在所有其他用戶中不受歡迎)作為結果。

一種語言可以有兩種描述方式,一種更抽象,另一種更具體。

一個例子是,在 Scheme 中,

(define (f x) (+ x x x))

導致評價

(f y)

與評價相同

((lambda (x) (+ x x x)) y)

與評價相同

(let ((x y)) (+ x x x))

與評價相同

(+ y y y)

請注意,我們還沒有說明所有這些是如何實現的。


另一種方法是參考機器中特定實現的細節。

因此,對於 Common Lisp / Emacs Lisp,我們首先討論語言運行時系統中的真實內存對象,稱為符號

一個符號有這個和那個——它就像一個具有多個字段的結構,可以用一些數據填充或留空。 一個符號的內存表示,內存中的一個實際結構,有一個叫做“可變單元格”的字段,它有一個叫做“函數單元格”的字段,你有什么。

當我們調用(fset 'f (lambda (x) (+ xxx))) ,我們將計算(lambda (x) (+ xxx))形式的結果存儲在符號F“函數單元格”中

如果我們在此之后調用(+ f 2) ,則會查看F“變量單元格”以找出其作為變量的值,從而導致“未定義變量”錯誤。

如果我們調用(f 2) ,則會查看F“函數單元格”以找出其作為函數的值(這也是(symbol-function 'f)也在做的事情)。 發現它保存了求值(lambda (x) (+ xxx)) ,因此進行了等價於((lambda (x) (+ xxx)) 2)的函數調用。


編輯:如果要將存儲在符號“變量單元格”中的函數作為函數調用,則需要使用funcall ,它將符號的值作為變量訪問,並將其用作函數。 在 Common Lisp (CLISP) 中,另一種 Lisp-2 語言:

[14]> (setq a (lambda (x) (+ x x x)))
#<FUNCTION :LAMBDA (X) (+ X X X)>
[15]> (funcall a 3)
9
[16]> (symbol-value 'a)
#<FUNCTION :LAMBDA (X) (+ X X X)>
[17]> (let ((x (symbol-value 'a))) (funcall x 3))
9
[18]> (let ((x 1)) (setf (symbol-function 'x) (symbol-value 'a)) (x 3))
9
  • setf是 Common Lisp 的“set place”原語
  • (setq a <val>)(setf (symbol-value 'a) <val>)
  • symbol-value訪問符號的變量單元格(其值作為變量)
  • symbol-function訪問符號的函數單元格(作為函數的值)
  • (funcall x 3)獲取(symbol-value 'x)並以3作為參數調用結果
  • (x 3)獲取(symbol-function 'x)並以3作為參數調用結果

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM