第十章 賦值

2018-02-24 15:45 更新

10.1?簡介

因?yàn)镾cheme是函數(shù)式語言,通常來說,你可以編寫不使用賦值的語句。然而,如果使用賦值的話,有些算法就可以輕易實(shí)現(xiàn)了。尤其是內(nèi)部狀態(tài)和繼續(xù)(continuations )需要賦值。

盡管賦值非常習(xí)見并且易于理解,但它有一些本質(zhì)上的缺陷。參見《計(jì)算機(jī)程序的構(gòu)造和解釋》的第三章第一節(jié)“賦值和局部狀態(tài)”以及《為什么函數(shù)式編程如此重要》

R5RS中規(guī)定的用于賦值的特殊形式是set!、set-car!set-cdr!、string-set!vector-set!等。除此之外,有些實(shí)現(xiàn)也依賴于賦值。由于賦值改變了參數(shù)的值,因此它具有破壞性(destructive)。Scheme中,具有破壞性的方法都以!結(jié)尾,以警示程序員。

10.2?set!

set!可以為一個(gè)參數(shù)賦值。與Common Lisp不同,set!無法給一個(gè)S-表達(dá)式賦值。 賦值前參數(shù)應(yīng)被定義。

(define var 1)
(set! var (* var 10))
var ? 10

(let ((i 1))
    (set! i (+ i 3))
    i)
? 4

10.3?賦值和內(nèi)部狀態(tài)

10.3.1?靜態(tài)作用域(詞法閉包)

Scheme中變量的作用域被限定在了源碼中定義其的那個(gè)括號(hào)里。作用域與源代碼書寫方式一致的作用域稱為“詞法閉包(Lexical closure)”“靜態(tài)作用域(Static scope)”。This way of scope eliminates bags, as you can grasp the scope of parameters quite easily — written on the source code.另一方面,還有一種被稱為“動(dòng)態(tài)作用域(Dynamic scope)”的作用域。這種作用域僅在程序運(yùn)行時(shí)確定。由于會(huì)在調(diào)試時(shí)帶來種種問題,這種作用域現(xiàn)在已經(jīng)不再使用。

特殊形式letlambda、letrec生成閉包。lambda表達(dá)式的參數(shù)僅在函數(shù)定義內(nèi)部有效。let只是lambda的語法糖,因此二者無異。

10.3.2?使用賦值和詞法閉包來實(shí)現(xiàn)內(nèi)部狀態(tài)

你可以使用詞法閉包來實(shí)現(xiàn)帶有內(nèi)部狀態(tài)的過程。例如,用于模擬銀行賬戶的過程可以按如下的方式編寫:初始資金是10美元。函數(shù)接收一個(gè)整形參數(shù)。正數(shù)表示存入,負(fù)數(shù)表示取出。為了簡單起見,這里允許存款為負(fù)數(shù)。

(define bank-account
  (let ((balance 10))
    (lambda (n)
      (set! balance (+ balance n))
      balance)))

該過程將存款賦值為(+ balance n)。下面是調(diào)用這個(gè)過程的結(jié)果。

The procedure assigns?(+ balance n)?to the?balance.??Following is the result of calling this function.

(bank-account 20)     ; donating 20 dollars 
;Value: 30

(bank-account -25)     ; withdrawing 25 dollars
;Value: 5

因?yàn)樵赟cheme中,你可以編寫返回過程的過程,因此你可以編寫一個(gè)創(chuàng)建銀行賬戶的函數(shù)。這個(gè)例子喻示著使用函數(shù)式程序設(shè)計(jì)語言可以很容易實(shí)現(xiàn)面向?qū)ο蟪绦蛟O(shè)計(jì)語言。實(shí)際上,只需要在這個(gè)基礎(chǔ)上再加一點(diǎn)東西就可以實(shí)現(xiàn)一門面向?qū)ο蟪绦蛟O(shè)計(jì)語言了。

(define (make-bank-account balance)
  (lambda (n)
    (set! balance (+ balance n))
    balance))
(define gates-bank-account (make-bank-account 10))   ; Gates makes a bank account by donating  10 dollars
;Value: gates-bank-account

(gates-bank-account 50)                              ; donating 50 dollars
;Value: 60

(gates-bank-account -55)                             ; withdrawing 55 dollars
;Value: 5

(define torvalds-bank-account (make-bank-account 100))  ; Torvalds makes a bank account by donating 100 dollars
;Value: torvalds-bank-account

(torvalds-bank-account -70)                             ; withdrawing 70 dollars
;Value: 30

(torvalds-bank-account 300)                             ; donating 300 dollars
;Value: 330

10.3.3?副作用

Scheme過程的主要目的是返回一個(gè)值,而另一個(gè)目的則稱為副作用(Side Effect)。賦值和IO操作就是副作用。

練習(xí) 1

修改make-bank-account函數(shù) Modify?make-bank-account?so that withdrawing more than balance causes error.?> hint: Use?begin?to group more than one S-expressions.

10.4?表的破壞性操作(set-car!,set-cdr!)

函數(shù)set-cat!和set-cdr!分別為一個(gè)cons單元的car部分和cdr部分賦新值。和set!不同,這兩個(gè)操作可以為S-表達(dá)式賦值。

(define tree '((1 2) (3 4 5) (6 7 8 9)))

(set-car! (car tree) 100)  ; changing 1 to 100 

tree
 ((100 2) (3 4 5) (6 7 8 9))

(set-cdr! (third tree) '(a b c)) ; changing  '(7 8 9) to '(a b c) 

tree
? ((100 2) (3 4 5) (6 a b c))

10.4.1?隊(duì)列

隊(duì)列可以用set-car!和set-cdr!實(shí)現(xiàn)。隊(duì)列是一種先進(jìn)先出(First in first out, FIFO)的數(shù)據(jù)結(jié)構(gòu),表則是先進(jìn)后出(First in last out,F(xiàn)ILO)。圖表1展示了隊(duì)列的結(jié)構(gòu)。cons-cell-top的car部分指向表(頭),而(cons-cell-top的)cdr部分指向表末的cons單元(表尾)。

入隊(duì)操作按如下步驟進(jìn)行(見圖表2): 1. 將當(dāng)前最末的cons單元(可以通過cons-cell-top取得)的cdr部分重定向到新的元素。 2. 將cons-cell-top的cdr部分重定向到新的元素

出隊(duì)操作按如下步驟進(jìn)行(見圖表3) 1. 將隊(duì)首元素存放在一個(gè)局部變量里。 2. 將cons-cell-top的car部分重定向到表的第二個(gè)元素

[代碼片段1]展示了如何實(shí)現(xiàn)隊(duì)列。函數(shù)enqueue!返回將元素obj添加進(jìn)隊(duì)列queue后的隊(duì)列。函數(shù)dequeue!將隊(duì)列的首元素移出隊(duì)列并將該元素的值作為返回值。

[代碼片段1] 隊(duì)列的Scheme實(shí)現(xiàn)

(define (make-queue)
  (cons '() '()))

(define (enqueue! queue obj)
  (let ((lobj (cons obj '())))
    (if (null? (car queue))
    (begin
      (set-car! queue lobj)
      (set-cdr! queue lobj))
    (begin
      (set-cdr! (cdr queue) lobj)
      (set-cdr! queue lobj)))
    (car queue)))

(define (dequeue! queue)
  (let ((obj (car (car queue))))
    (set-car! queue (cdr (car queue)))
    obj))
(define q (make-queue))
;Value: q

(enqueue! q 'a)
;Value 12: (a)

(enqueue! q 'b)
;Value 12: (a b)

(enqueue! q 'c)
;Value 12: (a b c)

(dequeue! q)
;Value: a

q
;Value 13: ((b c) c)

10.5?小結(jié)

這一章中,我講解了賦值和變量的作用域。雖然在Scheme中,賦值并不常用,但它對(duì)于某些算法和數(shù)據(jù)結(jié)構(gòu)來說是必不可少的。濫用賦值會(huì)讓你的代碼丑陋。當(dāng)萬不得已時(shí)才使用賦值!在后面的幾章里,我會(huì)介紹Scheme中的數(shù)據(jù)結(jié)構(gòu)。

10.6?習(xí)題解答

10.6.1?練習(xí)1

(define (make-bank-account amount)
  (lambda (n)
    (let ((m (+ amount n)))
      (if (negative? m)
      'error
      (begin
        (set! amount m)
        amount)))))
以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)