【Scheme归纳】6 赋值

赋值

因为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


时间: 2024-08-22 09:38:06

【Scheme归纳】6 赋值的相关文章

【Scheme归纳】3 比较do, let, loop

对象的比较 eq? 这个函数用来比较2个对象的地址,如果相同的话就返回#t.在Scheme中真用#t表示,假则用#f. 例如,(eq? str str)返回#t,因为str本身的地址的是一样的,但是"scheme"和"scheme"则被存储在不同的地址中,因此函数返回#f.注意,不要用eq?来比较数字,因为在R5RS和MIT-Scheme中均没有被指定返回值,建议使用eqv?或者=代替.以下是一些示例: (define str "scheme")

【Scheme归纳】5 数据结构

Scheme的数据结构 在前面的博文中我们使用了list等等,像其他的编程语言一样,Scheme也有字符(Character),字符串(String),符号(Symbol),向量(Vector)等数据结构.下面我们来一一介绍. 字符 在某个字符前添加#\来表面该物是一个字符.例如,#\a表示字符a. \Space,#\Tab,#\Linefeed,#\Return分别代表空格(Space),制表符(Tab),换行(linefeed)和返回(Return). (char-whitespace?#\

【Scheme归纳】1 使用Edwin

Edwin介绍 Edwin是MIT Scheme系统的一个窗口式的编辑使用前端.启动Edwin实际是先启动Scheme系统,再启动也给Edwin前端.Edwin是一个使用Scheme写的交互式编辑器,其特点是支持Scheme表达式的编辑和求职. 两种模式 Edwin模式: 编辑Scheme文件的模式,如果装入一个.scm文件,相应的Edwin的这个编辑区处于Edwin模式.这种模式下可以编写Scheme程序,也可以对表达式求值.正常求出的值显示在最下面交互行,但不会显示出错信息,也不能进入调试系

【Scheme归纳】4 高阶函数

高阶函数的介绍 高阶函数的英文名称是Higher Order Function,它们是以函数为参数的函数.主要用于映射(mapping).过滤(filtering).归档(folding)和排序(sorting)表.高阶函数让程序更具模块性,让函数更加通用. 函数sort具有2个参数,一个是需要排序的表,另一个是定序(Ordering)函数.下面展示了按照大小将一个整数表正序排序.而<函数就是本例中函数的定序函数. (sort'(420 -130 138 983 0298 783 -783) <

【Scheme归纳】7 常用关键字

display 在common lisp中有format,在scheme中则有display,轻松应对各种输出. (display(+ 1 2 3 4)) 10 ;Unspecifiedreturn value (display'(1 2 3 4)) (12 3 4) ;Unspecifiedreturn value newline 换行符一枚 trace trace可以用来跟踪函数的调用.我们用一个简单的例子来展示: (define(cube x) (* x x x)) (define(sum

【Scheme归纳】2 算数运算

quotient.remainder.modulo和sqrt 函数quotient用于求商数(quotient). 函数remainder和modulo用于求余数(remainder). 函数sqrt用于求参数的平方根(square root). 以下是一些示例: (quotient73) ;Value:2 (modulo73) ;Value:1 (sqrt 10) ;Value:3.1622776601683795 sin.cos.tan.asin.acos和atan atan接受1个或2个参

霍纳规则(C/C++,Scheme)

一.背景 霍纳(Horner)规则是采用最少的乘法运算策略,来求多项式 A(x)=anxn+an−1xn−1+...+a1x+a0 在x0处的值. 该规则为 A(x0)=(...((anx0+an−1)x0+...+a1)x0+a0) 二.分析 如果光看着式子或许会有点烦躁,不妨手动设定几个值到式子中去来手工运算一番,这样一来也会有些亲身的理解. 通过分解我们注意到,从右往左来看,每一个小式子都是如此: something∗x0+ai 三.代码 C语言版 #include <stdio.h>

【SICP归纳】2 高阶函数和数据抽象

上一篇博文对应的是书中的第一章的一二两节,我们已经大致的有了一种构造的感觉不是么.书中展示了很多有趣的句法(syntax).现在我们要让思想进一步的抽象,写这篇博客的时候并未学完整本书,更不敢说对书中的内容有一个多深的领悟.但我一路学习过来,就感觉书中的示例越来越抽象,作者所引导我们的也是如此方向.博文也会持续更新下去,伴随着我的理解. 在这个专栏的[Scheme归纳]4 高阶函数中已经初步介绍了什么是高阶函数(Higher-order Procedures).而在这一节中,将用高阶函数来做抽象

&lt;font color=&quot;red&quot;&gt;[置顶]&lt;/font&gt;

Profile Introduction to Blog 您能看到这篇博客导读是我的荣幸,本博客会持续更新,感谢您的支持,欢迎您的关注与留言.博客有多个专栏,分别是关于 Windows App开发 . UWP(通用Windows平台)开发 . SICP习题解 和 Scheme语言学习 . 算法解析 与 LeetCode等题解 . Android应用开发 ,而最近会添加的文章将主要是算法和Android,不过其它内容也会继续完善. About the Author 独立 Windows App 和