【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


目录
相关文章
|
7月前
|
存储 C++ 容器
学会在 C++ 中使用变量:从定义到实践
C++中的变量是数据容器,包括`int`、`double`、`char`、`string`和`bool`等类型。声明变量时指定类型和名称,如`int myNum = 15;`。`cout`与`&lt;&lt;`用于显示变量值。常量用`const`声明,值不可变。变量名应唯一,遵循特定命名规则,常量声明时需立即赋值。
163 1
|
2月前
学习使用register定义变量的方法
学习使用register定义变量的方法。
36 4
|
7月前
|
JavaScript 前端开发
js开发:请解释什么是ES6的解构赋值(destructuring assignment),并给出一个示例。
ES6的解构赋值简化了JavaScript中从数组和对象提取数据的过程。例如,`[a, b, c] = [1, 2, 3]`将数组元素赋值给变量,`{name, age} = {name: &#39;张三&#39;, age: 18}`则将对象属性赋值给对应变量,提高了代码的可读性和效率。
37 3
|
存储 C++
【Scheme】编程学习 (三) —— 闭包
本节主要讲述 Scheme 中闭包概念及使用
111 1
|
存储 算法 编译器
C#OOP之二 变量和表达式
C#OOP之二 变量和表达式
42 0
|
C语言 C++
【Scheme】编程学习 (四) —— 递归
Scheme 编程通常的使用方法为递归
115 0
|
自然语言处理 C语言 C++
【Scheme】编程学习 (二) —— 基础
Scheme 编程语言学习第二节基础
134 0
|
C++ 编译器
读书笔记 effective c++ Item 46 如果想进行类型转换,在模板内部定义非成员函数
1. 问题的引入——将operator*模板化 Item 24中解释了为什么对于所有参数的隐式类型转换,只有非成员函数是合格的,并且使用了一个为Rational 类创建的operator*函数作为实例。
1034 0
|
C++
读书笔记 effective c++ Item 37 永远不要重新定义继承而来的函数默认参数值
从一开始就让我们简化这次的讨论。你有两类你能够继承的函数:虚函数和非虚函数。然而,重新定义一个非虚函数总是错误的(Item 36),所以我们可以安全的把这个条款的讨论限定在继承带默认参数值的虚函数上。
1104 0