# 斯坦福编程范式 CS107_23

# Scheme 内存模型

>'(1 2 3)

对于上述的数据,他的内存结构如下所示。

image-20231224184041271

当使用以下命令时,scheme 会创建一个全局符号表,这个东西交 seq ,它会与这个变量关联起来。

>(define seq '(1 2 3))

image-20231224184242274

当使用 cons 命令时,如下所示。cons 在合并两个列表的时候其实是构造了一个新列表,如下图所示。

>(cons '(1 2 3) '(4 5 6))

image-20231224203352575

因此,输出的结果是如下所示的。

((1 2 3) 4 5 6)

下面让我们用两种方式构建一个列表:

((1 2) 1 2)

# 第一种方法

>(cons '(1 2) '(1 2))

这种方法和上面描述 cons 的使用大差不差,我们主要来介绍第二种方法。

# 第二种方法

使用如下内存存储方式:

image-20231224204718717

如果想要构造如上所示的列表,那么我们要用到一点技巧。

((lambda(x) (cons x x)) '(1 2))

通过上述代码我们构造了一个列表,并且将这个列表绑定到另一个上面,该变量在该定义中用到两次。最终使 carcdr 域都指向同一个内容。

# 改造我们之前的只能处理一元的 map

先把之前的那个一元的 map 重写一遍:

(define (unary-map fn seq)
  (if (null? seq) ()
      (cons (fn (car seq))
            (unary-map fn
                       (cdr seq)))))

为了使这个 map 能够容纳多个参数,我们需要使用 ...scheme 中。

如下例子所示,这个函数 bar 在接受完参数 a、b、c 之后,剩下的所有参数都需要以列表的形式存放在参数 d 中再传入给函数。随后我们再用 list 将这些参数存放在一个列表中,我们看看随意调用会发生什么。

(define (bar a b c . d)
  (list a b c d))
>(bar 1 2 3 4 5 6)
(1 2 3 (4 5 6))
>(bar 1 2 3)
(1 2 3 ())

这个例子显示出点在 scheme 中的具体用法。

下面我们来构造我们的一般性的 map 函数,它会需要用到我们之前的一元参数的 map 函数,所以我们在开头提前把它写了出来。

# map 的实现思想

我们最终的 map 要实现 scheme 中一模一样的功能:

>(map car '((1 2) (3 4) (5 6 7)))
(1 3 5)
>(map + '(1 2) '(10 20) '(100 400))
(111 422)
>(map * '(1) '(2) '(3) '(4) '(5))
(120)

简单起见。我们先假设,first-list 这一列表,与 other-lists 里面的所有列表长度相同。

(define (map fn first-list . other-lists)
  (if (null? first-list) '()
      (cons (apply fn (cons (car first-list) (unary-map car other-lists)))
            (apply map
                   (cons fn
                         (cons (cdr first-list)
                               (unary-map cdr other-lists))))

我们来举一个例子来对上述复杂的代码进行测试。

>(map list (1 10 100) (2 20 200) (3 30 300))

第一次调用第一个接收第一块数据 (1 10 100) 将它绑定到 first-list 上;其他的 ((2 20 200) (3 30 300)) 都绑定到 other-lists 上; 使用 unary-map list other-lists 我们得到了 (2 3) ;随后是 cons (car first-list) (unary-map list other-lists) 得到了 (1 2 3) ;随后我们对上述结果使用 apply fnapply list 最终得到了 ((1 2 3))

接下来递归的部分,对 other-listscdr 部分,即 ((20 200) (30 300)) 进行 map,之后我们使用 cons (cdr first-list) 把第一个列表中的 cdr 部分加在它们前面;得到 ((10 100) (20 200) (30 300)) ;然后再把 fn 放在这整个部分的最前面,得到 (list (10 100) (20 200) (30 300)) ;最后再使用 apply map 对这整个部分进行递归计算,即 (map list (10 100) (20 200) (30 300))

# Scheme 中的内存创建或释放

假设我们执行如下所示的代码:

(define x '(1 2 3))
(define y (cdr x))

在执行第一行代码的时候,列表 (1 2 3) 被绑定到 x 上,内存结构如下:

image-20231225143632571

执行第二行代码的时候, cdr x 被绑定到 y 上,即内存结构如下所示:

image-20231225143722610

当我想将 x 的值换一下的时候:

(define x '(4 5))

内存结构如下:

image-20231225144125955

y 的值不会改变,它仍然指向 '(2 3) ,这个时候 1 就成了没有使用的垃圾内存。

系统会为这块无用内存保留一段时间,它可能在等待 IO,也可能是在等待网络连接等等,当它没有任务的时候,它就会前往垃圾收集站,去回收一些没用的内存。