赋值
因为Scheme是函数式语言,通常来说,你可以编写不使用赋值的语句。然后如果使用赋值的话,有些算法就可以轻易实现了。尤其是内部状态和继续(continuations)需要赋值。
R5RS中规定的用于赋值的特殊形式是set!,set-car!,set-cdr!,string-set!,vector-set!等。
因为赋值改变了参数的值,因此它具有破坏性(destructive)。
在Scheme中,具有破坏性的方法都以!结尾,以警示程序员。
set!可以为一个参数赋值。与Common Lisp不同,set!无法给一个S-表达式赋值。另外,在赋值前参数应该被定义。例如:
(define num 10) ;Value: num (set! num (* num num)) ;Value: 100 (let ((n 0)) (set!n (+ n 3)) n) ;Value: 3
赋值和内部状态
Scheme中变量的作用被限定在了源码中定义其的那个括号里。作用域与源代码书写方式一致的作用域称为“词法闭包(Lexical closure)”或“静态作用域(Static scope)”。
另一方面,还有一种被称为“动态作用域(Dynamic scope)”的作用域。这种作用域仅在程序运行时确定。但由于会在调试时带来种种问题,这种作用域现在已经不再使用。
特殊形式let,lambda,letrec生成闭包。lambda表达式的参数仅在函数定义内部有效。let只是lambda的语法糖,因此二者无异。
你可以使用词法闭包来实现带有内部状态的过程。我们通过一个简单的银行账户的例子来展示。
(define bank-account
(let ((balance 100))
(lambda (n)
(set!balance (+ balance n))
balance)))
该过程将存款赋值为(+ balance n),下面是调用这个过程的结果。
(bank-account 100)
;Value: 200
(bank-account -50)
;Value: 150
因为在Scheme中,你可以编写返回过程的过程,因此你可以编写一个创建银行账号的函数。这个例子预示着使用函数式程序设计语言可以很容易实现面向对象程序设计语言。
(define (make-bank-account balance)
(lambda(n)
(set!balance (+ balance n))
balance))
(define bill-bank-account (make-account 100))
;Value: bill-bank-account
(bill-bank-account 50)
;Value: 150
(bill-bank-account -100
;Value: 50
(define nomasp-bank-account (make-bank-account200))
;Value: nomasp-bank-account
(nomasp-bank-account -50)
;Value: 150
(nomasp-bank-account 250)
;Value: 400
副作用
Scheme过程的主要目的是返回一个值,而另一个目的则称为副作用(Side Effect)。赋值和IO操作就是副作用。
表的破坏性操作(set-car!,set-cdr!)
函数set-car!和set-cdr!分别为一个cons单元的car部分和cdr部分赋新值。和set!不同,这两个操作都可以为S-表达式赋值。
(define list1 ‘((1 2) (3 4 5) (6 7 8 9)))
;Value: list1
(set-cdr! (third list1) ‘(a b c))
;Unspecified return value
list1
;Value: ((1 2) (3 4 5) (6 a b c))
队列
队列可以用set-car!和set-cdr!实现。队列是一种先进先出(First in first out, FIFO)的数据结构,表则是先进后出(Firstin last out, FILO)。
队列的Scheme实现
(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)))
(carqueue)))
(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)
定义语法,宏
用户定义语法称作宏(Macro)。Lisp/Scheme中的宏比C语言中的宏更加强大。宏可以使你的程序优美而紧凑。
宏是代码的变换。代码在被求值或编译前进行变换,and the procedure continues as if the transformed codes are writtenfrom the beginning.
你可以在Scheme中通过用符合R5RS规范的syntax-rules轻易地定义简单宏,相比之下,在Common Lisp中自定义语法就复杂多了。使用syntax-rules可以直接定义宏而不用担心变量的捕获(Variable Capture)。On the other hand,defining complicated macros that cannot be defined using the syntax-rules ismore difficult than that of the Common Lisp.
我将以一个简单的宏作为例子。
[代码片段 1]一个将变量赋值为’()的宏
(define-syntax nil!
(syntax-rules ()
((_x)
(set! x '()))))
syntax-reuls的第二个参数由是变换前表达式构成的表。_代表宏的名字。简言之,代码片段1表示表达式(nil! x)会变换为(set!x ‘()).
这类过程不能通过函数来实现,这是因为函数的闭包性质限制它不能影响外部变量。让我们来用函数实现代码片段1,并观察效果。
(define (f-nil! x)
(set!x '()))
(define a 1)
;Value: a
(f-nil! a)
;Value: 1
a
;Value: 1 ; the value of a dose not change
(nil! a)
;Value: 1
a
;Value: () ; a becomes '()
我会演示另外一个例子。我们编写宏when,其语义为:当谓词求值为真时,求值相应语句。
(define-syntax when
(syntax-rules ()
((_pred b1 ...)
(ifpred (begin b1 ...)))))
代码片段2中的…代表了任意多个数的表达式(包括0个表达式)。代码片段2揭示了诸如表达式(whenpred b1 …)会变换为(if pred (begin b1 …))。
由于这个宏是将表达式变换为if特殊形式,因此它不能使用函数来实现。下面的例子演示了如何使用when。
(let ((i 0))
(when(= i 0)
(display "i == 0")
(newline)))
i == 0
;Unspecified return value
我会演示两个实宏:while和for。只要谓词部分求值为真,while就会对语句体求值。而数字在指定的范围中,for就会对语句体求值。
(define-syntax while
(syntax-rules ()
((_pred b1 ...)
(let loop () (when pred b1 ... (loop))))))
(define-syntax for
(syntax-rules ()
((_(i from to) b1 ...)
(let loop((i from))
(when (< i to)
b1 ...
(loop (1+ i)))))))
下面演示了如何实用它们:
(define-syntax while
(syntax-rules ()
((_pred b1 ...)
(let loop () (when pred b1 ... (loop))))))
(define-syntax for
(syntax-rules ()
((_(i from to) b1 ...)
(let loop((i from))
(when (< i to)
b1 ...
(loop (1+ i)))))))
感谢访问,希望对您有所帮助。 欢迎关注或收藏、评论或点赞。
为使本文得到斧正和提问,转载请注明出处:
http://blog.csdn.net/nomasp