SICP-变动数据做模拟

本文介绍了如何使用LISP构建变动数据的计算模型,通过变动的状态和队列展示概念,进而构建数字电路模拟器,包括导线、反相器、与门和非门等部件。模拟器利用议程表处理延迟和事件,通过实验展示了4选1数据选择器、全加器、锁存器、JK触发器和移位寄存器等功能。
摘要由CSDN通过智能技术生成

变动的状态

唯一不变的就是变化本身。 —斯宾塞·约翰逊

为了构造出更符合人们对真实世界认知的计算模型,我们引入了变动的数据。将这个世界看作是由许多独立的对象构成,每个对象各自保有自己的局部状态,并且随着时间变化。

对应到程序语言中,就相当于引入赋值预算符。从此以后,代换模型就不再适用了(因为我们不能期待同一个函数在不同的时间点返回相同的值),我们需要去考虑更加复杂的环境模型

变动的队列(FIFO)

先进先出队列(FIFO),是一个典型的用来展示变动的数据结构的例子。

我们提供以下过程:

操作 效果
make-queue 创建空队列
empty-queue? (queue) 判断是否空队列
front-queue (queue) 返回队列的第一个元素(不修改队列)
insert-queue! (queue) (item) 将相应元素插入队列末端
delete-queue! (queue) 删除队列前端的元素

下面是具体实现(common lisp)

;;queue
(defun front-ptr (queue)
  (car queue))

(defun rear-ptr (queue)
  (cdr queue))

(defun set-front-ptr! (queue item)
  (setf (car queue) item))

(defun set-rear-ptr! (queue item)
  (setf (cdr queue) item))

(defun empty-queue? (queue)
  (null (front-ptr queue)))

(defun make-queue ()
  (cons '() '()))

(defun front-queue (queue)
  (if (empty-queue? queue)
      (error "FRONTED called with an empty queue")
    (car (front-ptr queue))))

(defun insert-queue! (queue item)
  (let ((new-pair (cons item '())))
    (cond ((empty-queue? queue)
	   (set-front-ptr! queue new-pair)
	   (set-rear-ptr! queue new-pair)
	   queue)
	  (t 
	   (setf (cdr (rear-ptr queue)) new-pair)
	   (set-rear-ptr! queue new-pair)
	   queue))))

(defun delete-queue! (queue)
  (cond ((empty-queue? queue)
	 (error "DELETE! called with an empty queue"))
	(t
	 (set-front-ptr! queue (cdr (front-ptr queue)))
	 queue)))

变动的系统(数字电路模拟器)

我们采用的计算模型类似于真实世界中的对象:用来传递信号的导线,基本的部件:反相器,与门,非门,以及更复杂的功能块。
同样地,输入信号的改变会影响到输出信号,而不同的部件也会有着不同的输出延迟

我们首先来构造最基本的,也是最麻烦的部件:导线

按照我们刚才的分析,一个导线包含了两个局部状态变量,一个是当前的信号值,另一个是一组过程(用来影响该导线连接着的其他部件的状态),当信号值改变时,所有包含的过程都需要运行,以通知相连部件做出相应动作。

(defun make-wire ()
  (let ((signal-value 0) (action-procedures '()))
    #'(lambda (m)
	(cond ((equal m 'get-signal) signal-value)
	      ((equal m 'set-signal!) 
	       #'(lambda (new-value)
		   (if (not (= signal-value new-value))
		       (progn (setf signal-value new-value)
			      (call-each action-procedures))
		     'done)))
	      ((equal m 'add-action!) 
	       #'(lambda (proc)
		   (setf action-procedures (cons proc action-procedures))
		   (funcall proc)))
	      (t (error "Unknown operation --WIRE"))))))

注意这里使用的前缀#’的λ-表达式,主要是为了提供闭包,让不同导线的信号值和过程互不影响。

比如下面这样写就不能提供闭包,而且会招致不易发觉的错误(不要问我怎么知道的):

(defun test ()
	(let ((value 0))
		(defun set-value (new-value)
			(setf value new-value))
		(defun dispatch (m)
			(cond ((equal m 'get-value) value)
				  ((equal m 'set-value) #'set-value)))
		#'dispatch))

所有变量会共享同一个值:
在这里插入图片描述
围绕着导线,我们来完善一下相关过程:

(defun call-each (procedures)
  (if (null procedures)
      'done
    (progn
      (funcall (car procedures))
      (call-each (cdr procedures)))))

(defun get-signal (wire)
  (funcall wire 'get-signal))

(defun set-signal! (wire new-value)
  (funcall (funcall wire 'set-signal!) new-value))
  
(defun add-action! (wire action-procedure)
  (funcall (funcall wire 'add-action!) action-procedure))

其中,call-each会依次调用导线上的所有过程。从前面可以看到,每次导线上的信号值改变后,相关的过程都会进行依次调用,这很容易理解:在理想情况下,同一根导线上的各个部分都应该是相同的状态。

接下来,让我们来实现最基本的部件:

反相器:

;;反相器
(defun inverter (input output)
  (defun invert-input ()
    (let ((new-value (logical-not (get-signal input))))
      (after-delay *inverter-delay*
		   (lambda () 
		     (set-signal! output new-value)))))
  (add-action! input #'invert-input)
  'ok)
;;逻辑反
(defun logical-not (s)
  (cond ((= s 0) 1)
	((= s 1) 0)
	(t (error "Invalid signal"))))

反相器构造了一个反相的过程,这个过程会在相应的延迟时间后执行赋值的操作。然后将这个构造出来的过程加入在输入线的过程集合里。

与门和非门也类似:

;;与门
(defun and-gate (a1 a2 output)
  (defun and-action-procedure ()
    (let ((new-value
	   (logical-and (get-signal a1) (get-signal a2))))
      (after-delay *and-gate-delay*
		   (lambda ()
		     (set-signal! output new-value)))))
  (add-action! a1 #'and-action-procedure)
  (add-action! a2 #'and-action-procedure)
  'ok)
;;逻辑与
(defun logical-and (s1 s2)
  (cond (
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值