首页 MIT-scheme
文章
取消

MIT-scheme

把MIT-scheme当作计算器使用

1.每个表达式d都由()圈住,当跳出最外层()时,会在前端输出值。

2.表达式之间可以嵌套,表达式形如(操作符 参数 参数 ...),而这种表达式也被称为S-表达式

3.每个操作符实际是个函数,既然MIT-scheme可以当作计算器使用,那么它也支持基本四大运算符,加、减、乘、除,分别对应+-*/

4.练习

练习 1

使用Scheme解释器计算下列式子:

1.(1+39) * (53-45)
2.(1020 / 39) + (45 * 2)
3.求和:39, 48, 72, 23, 91
4.求平均值:39, 48, 72, 23, 91(结果取为浮点数)

解答:
1.(* (+ 1 39) (+ 53 45))
2.(+ (/ 1020 39) (* 45 2))
3.(+ 39 48 72 23 91)
4.(exact->inexact (/ (+ 39 48 72 23 91) 5)) exact->inexact 分数->浮点数

5.指数函数exp和对数函数log

两者的底数都是e,只不过目前我不知道如何在scheme中表示e

6.expt,用来计算x的y次幂

例如 (expt 4 1/2),代表4的开平方

数据结构

练习1

使用cons来构建在前端表现为如下形式的数据结构。

1.("hi" . "everybody")                  (cons "hi" "everybody")
2.(0)                                   (cons 0 '())
3.(1 10 . 100)
4.(1 10 100)                            (cons 1 (cons 10 100))
5.(#\I "saw" 3 "girls")                 (cons #\I (cons "saw" (cons 3 (cons "girls" '()))))
6.("Sum of" (1 2 3 4) "is" 10)          (cons "Sum of" (cons (cons 1 (cons 2 (cons 3 (cons 4 '())))) (cons "is" (cons 10 '()))))

1.cons单元

cons单元由两部分构成,car和cdr,结构如下图:

cons单元

也就是说一个cons单元由两个内存单元,并且这两个内存单元存的均是地址,指向真实值的地址,所以cons单元可以理解为两个指针car、cdr组成的数据结构

通常用cons构造cons单元,形如(cons 1 2) => (1 . 2)

由于car,和cdr存的是指针,因而它们可以指向任意类型的值

另外(cons 1 2)表达式构造了一个cons单元,而cons单元实际是一块内存,因而它的返回值是一个地址,这也代表cons单元可以互相嵌套或者说进行串联

2.表

表是Cons单元通过用cdr部分连接到下一个Cons单元的开头实现的。这个定义显然具有递归性,即表(cons 1 表),用图表示如下:

表

其中'()表示空表,就算数据仅由一个Cons单元组成,只要它的cdr单元是’(),那它就是一个表。在练习1中2、4、5、6均是表结构

3.原子

不使用Cons单元的数据结构称为原子(atom)。数字,字符,字符串,向量和空表’()都是原子。’()既是原子,又是表。

4.引用

所有的记号都会依据Scheme的求值规则求值:所有记号都会从最内层的括号依次向外层括号求值,且最外层括号返回的值将作为S-表达式的值。而引用能够阻止记号被求值,形如(quote (+ 1 2)),将会返回(+ 1 2),而不是3,而quote一般简写为 ',(quote (+ 1 2)) == ‘(+ 1 2)。**实际上MIT-Scheme用()表示空表,不过在程序中表示空表应该用'(),显然'(1, 2, 3, 4)则代表(1, 2, 3, 4)表。

5.list函数

list函数的存在使得表的构建变得简单(代码编写更简单)

如练习1中所示,(“Sum of” (1 2 3 4) “is” 10)用cons构造是这样的:(cons “Sum of” (cons (cons 1 (cons 2 (cons 3 (cons 4 ‘())))) (cons “is” (cons 10 ‘())))),但是如果用list函数,它是这样的:(list “Sum of” ‘(1 2 3 4) “is” 10)

也就是说list接收任意数量的参数,并能将其构造串联成表

6.car函数,cdr函数

见名知意,分别是获取cons单元的car部分和cdr部分,如果是串联的cons单元,car获取的是第一个cons单元的car部分,而cdr获取的是第一个cons单元的cdr部分,也就是后面的整个串联的部分

函数

1.define和set!

使用define来将一个符号与一个值绑定。可以通过这个操作符定义例如数、字符、表、函数等任何类型的全局参数。形如 (define sig value)

2.自定义函数的两种方式

  • 用lambda定义过程
(define hello (lambda (name) 
    (string-append "hello " name "!")))

这里的调用很想Java中的lambda表达式,(args) -> {method body},只不过没有 ->,也许Java就是借鉴的scheme也说不定

  • 函数的短定义形式
(define (hello name)
    (string-append "hello " name "!"))

函数的短定义与函数的调用格式非常相像,并且注意函数体可以是多个S—表达式

3.define 定义全局变量

; 定义全局变量name
(define name "chezscheme")

; 定义函数
(define hello (lambda (name) 
    (string-append "hello " name "!")))

; 调用函数
(hello name)

4.define和set!的区别

define定义的变量只在define所在的scope中可见,一个典型例子就是:在函数中define的变量在函数外部是无法访问的

而set!定义的变量才是真正的全局变量

eq?、eqv?、equal?

都只接收两个参数,但三者有小小的区别,eq?比较地址值;eqv?适合比较数字(按照值和类型进行比较,如1+2和2+1相等,但3和3.0不同,因为类型不同);equal?适合比较表和字符串等类似的序列

and、or

接收任意数量的参数,但这两者不等同于C语言或Java中的&&和 ,但却继承了它们的短路特性

这两个操作符都会对传入的参数挨个求值,and遇到#f返回,否则返回最后一个参数,or遇到非#f返回,否则返回最后一个参数

比较运算符=、<、>、<=、>=

这些操作符允许传入任意数量的参数,只要参数传入的顺序符合操作符的语义,则返回#t,否则返回#f

分支语句

1.if语句

形如:

(if predicate then_value else_value)

2.cond语句

Java或者C语言中的switch语句,形如:

(cond
  (predicate_1 clauses_1)
  (predicate_2 clauses_2)
    ......
  (predicate_n clauses_n)
  (else        clauses_else))

(define (fee age)
  (cond
   ((or (<= age 3) (>= age 65)) 0)
   ((<= 4 age 6) 0.5)
   ((<= 7 age 12) 1.0)
   ((<= 13 age 15) 1.5)
   ((<= 16 age 18) 1.8)
   (else 2.0)))

在clauses中可以写任意数量的表达式,但最终会返回最后一条表达式;不知道scheme中有没有return语句,这相当于return语句只能出现在语句最后了啊!

let定义局部变量

实际上,let表达式只是lambda表达式的一个语法糖:

(let ((p1 v1) (p2 v2) ...) exp1 exp2 ...)
;⇒
((lambda (p1 p2 ...)
    exp1 exp2 ...) v1 v2)
p是变量,v是给p赋的值,或者说,v绑定给p

尾递归

1.尾递归于递归

递归虽然可读性很强,但有两个明显的缺点:①占用内存(方法占用内存栈)②效率低(方法调用开销,要等待方法返回值)

而尾递归参数中用一个来存最终的结果,最终结果出来的时候就直接返回了;因此在效率上是要比普通递归要好的,实际上尾递归就是循环

命名let和letrec

命名let就是let定义有了名字,形如:

(define (fact-let n)
  (let loop((n1 n) (p n))           ; 1
    (if (= n1 1)                    
    p
    (let ((m (- n1 1)))
      (loop m (* p m))))))      ; 2

letrec和let很相似,但letrec能将一个过程(lambda定义的)与一个参数绑定(其实就相当定义了一个函数),形如:

(define (fact-letrec n)
  (letrec ((iter (lambda (n1 p)
           (if (= n1 1)
               p
               (let ((m (- n1 1)))
             (iter m (* p m)))))))     ; *
    (iter n n)))
(fact-letrec 1000)

流式计算

; 构建一个以整数1为起点的无穷整数流
(define (integers-from n)
  (cons-stream n
      (integers-from (+ n 1))))
; 流s1
(define s1 (integers-from 1))
; 流s2
(define s2 (integers-from 2))

; 获取指定索引值下的流中的元素
(define (nth-stream n s)
  (if (= n 0)
      (head s)
      (nth-stream (- n 1) (tail s))))

; 获取索引为1000000的流的元素
(nth-stream 1000000 s1)

; map流(对流的每个元素进行处理,最后返回流)
(define (scale-stream stream factor)
  (stream-map (lambda (x) (* x factor)) stream))

(nth-stream 0 (scale-stream s1 4))

; 合并流
(define (add-streams s1 s2)
  (cond ((empty-stream? s1) s2)
    ((empty-stream? s2) s1)
    (else
      (cons-stream
        (+ (head s1) (head s2))
        (add-streams (tail s1)
                    (tail s2))))))

; Fibonacci 的流式计算

(define fibs
  (cons-stream 0 
    (cons-stream 1
      (add-streams (tail fibs) fibs))))

(nth-stream 4 fibs)
本文由作者按照 CC BY 4.0 进行授权