詞法作用域 vs 動態作用域

2022-06-21 09:24:09 字數 3741 閱讀 7094

scheme是一門採用詞法作用域(lexical scoping)的lisp方言,這個設計是從alogol語言裡借鑑過來的。現在,詞法作用域已經被許多lisp方言所吸收,實踐表明,這的確是一項正確的設計,避免了很多奇怪的錯誤,比較符合人類的思維習慣。

但是,在某些場合下,動態作用域又是很有用的特性,比如emacs lisp裡面就預設採用動態作用域。

下面的程式演示了詞法作用域與動態作用域的不同

(define x 1)

(define y (lambda () x))

(let ([x 2]) (y))

如果是詞法作用域,返回1,如果是動態作用域,返回2。

從乙個簡單的例子出發,演示如何在scheme裡面實現動態作用域,我們想要寫乙個try catch巨集來處理程式中的異常。其中乙個函式叫做current-exception-handler,我們希望它是動態的,隨**執行位置而變化,永遠指向當前的異常處理器,下面是我們寫的第乙個版本

(define current-exception-handler

(lambda (msg) (error "no top level try")))

(define-syntax try

(syntax-rules (catch)

[(_ expr ... (catch msg expr* ...))

(call/cc (lambda (k)

(let ([msg (call/cc (lambda (k1)

(set! current-exception-handler k1)

(let ([result (begin expr ...)])

(k result)

)))])

expr* ...)))]))

(define (throw msg) (current-exception-handler msg))

其中,涉及throw的**必須被包含在try裡面,否則會導致錯誤。先來測試一下

(try 1

(throw 'foo)

(catch msg (display "catch ") (display msg)))

但是如果我們在後面再加上一行**

(throw 'test)

這裡就出現問題了,按照我們上面的要求throw應該在try catch塊裡面使用,而這裡卻不會報錯,說明我們上面的**錯了。

進入try catch塊時,我們把current-exception-handler設定為當前try catch塊的exception-handler,但當執行出try catch塊的時候,exception-handler並沒有發生變化,我們希望恢復原來的exception-handler,使得不論是正常退出或者是發生錯誤退出都能恢復原有的exception-handler,所以修改一下**,就是:

(define current-exception-handler

(lambda (msg) (error "no top level try")))

(define-syntax try

(syntax-rules (catch)

[(_ expr ... (catch msg expr* ...))

(call/cc (lambda (k)

(let ([msg (call/cc (let ([preserved current-exception-handler])

(lambda (k1)

(set! current-exception-handler (lambda (msg)

(set! current-exception-handler preserved) 

(k1 msg)))

(let ([result (begin expr ...)])

(set! current-exception-handler preserved)

(k result)

))))])

expr* ...)))]))

(define (throw msg) (current-exception-handler msg))

這樣,只要**出了try catch塊,current-exception-handler就會恢復成原來的,從而實現了動態作用域的效果。

如果你記得fluid-let語句的話,你就會發現上面的**效果和fluid-let語句很類似,沒錯,fluid-let語句就是被設計用來實現dynamic scoping效果的,fluid-let語句的定義如下(摘自chez scheme user guide 8):

(define-syntax fluid-let

(lambda (x)

(syntax-case x ()

[(_ () b1 b2 ...) #'(let () b1 b2 ...)]

[(_ ((x e) ...) b1 b2 ...)

(andmap identifier? #'(x ...))

(with-syntax ([(y ...) (generate-temporaries #'(x ...))])

#'(let ([y e] ...)

(let ([swap (lambda ()

(let ([t x]) (set! x y) (set! y t))

...)])

(dynamic-wind swap (lambda () b1 b2 ...) swap))))])))

具體的實現細節就不說了,給乙個例子:

(define x 1)

(define y (lambda () x))

(fluid-let ([x 2]) (y))

這和第一段**一模一樣,只不過用了fluid-let語句,就在scheme裡面模擬出了dynamic scoping的效果。

這樣,try catch巨集就可以寫的很簡單了:

(define-syntax try

(syntax-rules (catch)

[(_ expr ... (catch msg expr* ...))

(call/cc (lambda (k)

(let ([msg (call/cc (lambda (k1)

(fluid-let ([current-exception-handler k1])

(let ([result (begin expr ...)])

(k result)

))))])

expr* ...)))]))

但是出於某些原因(效率,賦值,標準庫裡面沒有提供。。),我們並不想用fluid-let語句來解決問題,恰好rnrs標準庫裡面就提供了乙個類似的,parameterize語句,同樣可以完成任務。

(define x (make-parameter 1))

(define y (lambda () (x)))

(parameterize ([x 2]) (y))

如果我們在parameterize塊外面嘗試呼叫y函式,就會得到1,這說明parameterize只對塊內生效,一旦出了parameterize塊(不論是通過何種方式),就會恢復成原來的樣子。

*************** end

詞法作用域 VS 動態作用域 VS 立即執行函式

js詞法作用域是由你在寫 時將變數和塊作用域寫在 來決定。js動態作用域和this機制息息相關。它的作用域詩是在執行的過程中確定 var a 1 function foo foo 1從上面的 我們可以看出 foo中列印a的值不是由寫 的位置確定的,而是取決於foo執行的位置。最小授權原則最小授權原則...

詞法作用域和動態作用域

作用域是指程式源 中定義變數的區域。它規定了如何查詢變數,也是就確定當前執行 對變數的訪問許可權。js 採用了詞法作用域 lexical scoping 也就是靜態作用域。js 採用的詞法作用域是靜態作用域,因此函式的作用域在函式定義的時候就決定了。與靜態作用域相對的是動態作用域,函式的作用域是在函...

詞法作用域

域表示的就是 範圍,即 作用範圍.就是乙個名字在什麼地方可以被使用,什麼時候不能使用.所謂的 詞法 作用域,就是 在編寫過程中體現出來的作用範圍.一旦寫好,不用執行,作用範圍就已經確定好了.這個就是所謂詞法作用域.這意味著函式執行在定義它的作用域中,而不是在呼叫它的作用域中。在 js 中詞法作用域規...