EoPL3笔记

Essentials of Programming Languages (3ed) 已经是许久以前读的了, 现在记下一些东西, 或许对于后来者有用.

第1章 归纳性的数据集合

引入了数据的归纳 (或者说递归) 定义, 尤其是BNF文法, 并介绍了与之匹配的编程技术和证明技术 (结构归纳法).

第2章 数据抽象

引入了数据抽象的想法, 也就是抽象数据类型和其实现的分离; 介绍了一个定义数据类型的工具, 以及解构的工具; 引入了抽象句法树的想法, 为之后的章节做铺垫.

第3章 表达式

引入了解释和编译的基本想法, 然后为几个小语言编写了解释器. 介绍了什么是词法作用域 (和动态作用域), 引入了词法寻址 (lexical addressing) 的想法. 在某种意义上, 这就是一个很好的编译的例子.

第4章 状态

引入了副作用, 其实就是赋值. 引入了两种不同的语言, 一种显式操作引用 (类似于SML那种), 还有一种和通常的赋值类似. 介绍了一些不同的参数传递机制, 例如按引用调用, 按名调用等.

第5章 延续传递解释器

介绍了延续和延续传递风格 (CPS), 将之前的解释器改为CPS风格的. 对于通常的没有尾调用优化的语言, 引入了蹦床的想法. 使用延续实现了异常和多线程机制.

练习5.30解答. 当然了, 我不会全然按照题目的要求完成. 首先, 我们先给出句法.

<exp> ::= <var>
       |  (proc (<var>) <exp>)
       |  (<exp> <exp>)
之所以用proc而不是lambda, 是为了避免让人误以为这是lambda演算. 然后, 我们给出实现.
(define EXP (void))
(define ENV (void))
(define VAL (void))
(define PRO (void))
(define CTX (void))
(define-syntax PUSH
  (syntax-rules ()
    ((_ val var)
     (set! var (cons val var)))))
(define-syntax POP
  (syntax-rules ()
    ((_ var)
     (let ((val (car var)))
       (set! var (cdr var))
       val))))
(define (APPLY_ENV)
  (cond ((assq EXP ENV)
         => (lambda (p)
              (set! VAL (cdr p))))
        (else
         (error 'APPLY_ENV
                "unbound variable ~s" EXP))))
(define (EVAL)
  (match EXP
    (,var
     (guard (symbol? var))
     (APPLY_ENV)
     (APPLY_CTX))
    ((proc (,var) ,body)
     (set! VAL EXP)
     (APPLY_CTX))
    ((,rator ,rand)
     (set! EXP rator)
     (PUSH `(RATOR_CTX ,rand) CTX)
     (EVAL))))
(define (APPLY_CTX)
  (unless (null? CTX)
    (define top (POP CTX))
    (match top
      ((RATOR_CTX ,rand)
       (set! EXP rand)
       (PUSH `(RAND_CTX ,VAL) CTX)
       (EVAL))
      ((RAND_CTX ,proc)
       (set! PRO proc)
       (APPLY_PRO))
      ((POP_ENV)
       (POP ENV)
       (APPLY_CTX)))))
(define (APPLY_PRO)
  (match PRO
    ((proc (,var) ,body)
     (set! EXP body)
     (PUSH (cons var VAL) ENV)
     (PUSH `(POP_ENV) CTX)
     (EVAL))))
(define (RUN exp)
  (set! EXP exp)
  (set! ENV '())
  (set! CTX '())
  (EVAL))
注记. 动态作用域有着动态窗口期, 也就是说, 对于形式参数的绑定恰在该过程返回时被弹出. 在某种意义上, 动态窗口期是一种静态性质.

练习5.53. 我仍然没有完全按照题目要求完成. 现在过程interp, apply-ctx, apply-closure都会多接受一个名为tid的参数, 意即线程标识符 (thread identifier).

#lang racket
(require "match.rkt" "env.rkt" "store.rkt" data/queue)
(define (make-counter)
  (let ((x -1))
    (lambda ()
      (set! x (+ x 1))
      x)))
(define new-thread-id
  (let ((counter (make-counter)))
    (lambda ()
      `(thread-id ,(counter)))))
(define (make-closure formal body env)
  (vector 'closure formal body env))
(define (closure-formal closure)
  (vector-ref closure 1))
(define (closure-body closure)
  (vector-ref closure 2))
(define (closure-env closure)
  (vector-ref closure 3))
(define (set-closure-env! closure env)
  (vector-set! closure 3 env))
(define ((apply-closure tid) closure arg ctx)
  (let ((formal (closure-formal closure))
        (body (closure-body closure))
        (env (closure-env closure)))
    ((interp tid)
     body (extend-env formal arg env) ctx)))
(define (decompose binding*)
  (if (null? binding*)
      (values '() '() '())
      (let ((binding (car binding*))
            (binding* (cdr binding*)))
        (let-values (((f* x* fbody*) (decompose binding*)))
          (match binding
            ((,f (lambda (,x) ,fbody))
             (values (cons f f*)
                     (cons x x*)
                     (cons fbody fbody*))))))))
(define (extend-env* var* val* env)
  (append (map cons var* val*) env))
(define (extend-env-rec f* x* fbody* env)
  (let* ((closure*
          (map (lambda (x fbody) (make-closure x fbody (void))) x* fbody*))
         (ref* (map newref closure*))
         (env^ (extend-env* f* ref* env)))
    (for-each (lambda (closure)
                (set-closure-env! closure env^))
              closure*)
    env^))
(define global-store (make-store 10000))
(define (newref val)
  (global-store 'newref val))
(define (deref ref)
  (global-store 'deref ref))
(define (setref ref val)
  (global-store 'setref ref val))
(define (make-scheduler name ticks)
  (define queue (void))
  (define time (void))
  (define value (void))
  (define (add-thread thread)
    (enqueue! queue thread))
  (define (run-next-thread)
    (if (queue-empty? queue)
        (printf "The final answer is ~s." value)
        (let ((thread (dequeue! queue)))
          (set! time ticks)
          (thread))))
  (define (expired?)
    (zero? time))
  (define (count-down)
    (set! time (- time 1)))
  (define (set-answer answer)
    (set! value answer))
  (define (initialize)
    (set! queue (make-queue))
    (set! time ticks)
    (set! value (void)))
  (define (set-ticks new-ticks)
    (set! ticks new-ticks))
  (lambda (msg . arg*)
    (case msg
      ;I use apply to make sure the number of arguments
      ;matches the arity of the operation.
      ((add-thread) (apply add-thread arg*))
      ((run-next-thread) (apply run-next-thread arg*))
      ((expired?) (apply expired? arg*))
      ((count-down) (apply count-down arg*))
      ((set-answer) (apply set-answer arg*))
      ((initialize) (apply initialize arg*))
      ((set-ticks) (apply set-ticks arg*))
      (else (error name "unknown message ~s" msg)))))
(define global-scheduler
  (make-scheduler 'global-scheduler 10))
(define (make-mutex closed? queue)
  (vector 'mutex closed? queue))
(define (mutex? x)
  (and (vector? x)
       (= (vector-length x) 3)
       (eq? (vector-ref x 0) 'mutex)))
(define (mutex-closed? mutex)
  (vector-ref mutex 1))
(define (mutex-queue mutex)
  (vector-ref mutex 2))
(define (set-mutex-closed?! mutex closed?)
  (vector-set! mutex 1 closed?))
(define (build-mutex)
  (make-mutex #f (make-queue)))
(define (wait mutex thread)
  (unless (mutex? mutex)
    (error 'wait "~s is not a mutex" mutex))
  (cond ((mutex-closed? mutex)
         (enqueue! (mutex-queue mutex) thread)
         (global-scheduler 'run-next-thread))
        (else
         (set-mutex-closed?! mutex #t)
         (thread))))
(define (signal mutex thread)
  (unless (mutex? mutex)
    (error 'signal "~s is not a mutex" mutex))
  (when (mutex-closed? mutex)
    (define q (mutex-queue mutex))
    (if (queue-empty? q)
        (set-mutex-closed?! mutex #f)
        (global-scheduler
         'add-thread
         (dequeue! q))))
  (thread))
(define ((apply-ctx tid) ctx val)
  (cond ((global-scheduler 'expired?)
         (global-scheduler
          'add-thread
          (lambda ()
            ((apply-ctx tid) ctx val)))
         (global-scheduler
          'run-next-thread))
        (else
         (global-scheduler 'count-down)
         (match ctx
           ((wait-ctx ,ctx)
            (wait val
                  (lambda ()
                    ((apply-ctx tid) ctx (void)))))
           ((signal-ctx ,ctx)
            (signal val
                    (lambda ()
                      ((apply-ctx tid) ctx (void)))))
           ((if-ctx ,e2 ,e3 ,env ,ctx)
            (if val
                ((interp tid) e2 env ctx)
                ((interp tid) e3 env ctx)))
           ((set!-ctx ,x ,env ,ctx)
            (let ((ref (apply-env env x)))
              (setref ref val)
              ((apply-ctx tid) ctx (void))))
           ((let-ctx ,x ,body ,env ,ctx)
            ((interp tid)
             body
             (extend-env x (newref val) env)
             ctx))
           ((begin-ctx ,e* ,env ,ctx)
            ((interp tid) `(begin . ,e*) env ctx))
           ((print-ctx ,ctx)
            (printf "~s\n" val)
            ((apply-ctx tid) ctx (void)))
           ((spawn-ctx ,ctx)
            (define new-tid (new-thread-id))
            (global-scheduler
             'add-thread
             (lambda ()
               ((apply-closure new-tid)
                val (newref tid)
                '(end-subthread-ctx))))
            ((apply-ctx tid) ctx new-tid))
           ((op-ctx-1 ,op ,e2 ,env ,ctx)
            ((interp tid)
             e2 env
             `(op-ctx-2 ,op ,val ,ctx)))
           ((op-ctx-2 ,op ,v1 ,ctx)
            (let ((v (case op
                       ((+) (+ v1 val))
                       ((-) (- v1 val))
                       ((*) (* v1 val))
                       ((=) (= v1 val))
                       ((eq?) (eq? v1 val)))))
              ((apply-ctx tid) ctx v)))
           ((rator-ctx ,rand ,env ,ctx)
            ((interp tid)
             rand env
             `(rand-ctx ,val ,ctx)))
           ((rand-ctx ,closure ,ctx)
            ((apply-closure tid)
             closure (newref val) ctx))
           ((end-main-thread-ctx)
            (global-scheduler
             'set-answer val)
            (global-scheduler
             'run-next-thread))
           ((end-subthread-ctx)
            (global-scheduler
             'run-next-thread))))))
(define ((interp tid) exp env ctx)
  (match exp
    (,int
     (guard (integer? int))
     ((apply-ctx tid) ctx int))
    (,bool
     (guard (boolean? bool))
     ((apply-ctx tid) ctx bool))
    ((quote ,sym)
     (guard (symbol? sym))
     ((apply-ctx tid) ctx sym))
    ((void)
     ((apply-ctx tid) ctx (void)))
    (,var
     (guard (symbol? var))
     ((apply-ctx tid)
      ctx (deref (apply-env env var))))
    ((mutex)
     ((apply-ctx tid) ctx (build-mutex)))
    ((wait ,e)
     ((interp tid) e env `(wait-ctx ,ctx)))
    ((signal ,e)
     ((interp tid) e env `(signal-ctx ,ctx)))
    ((if ,e1 ,e2 ,e3)
     ((interp tid)
      e1 env
      `(if-ctx ,e2 ,e3 ,env ,ctx)))
    ((set! ,x ,e)
     ((interp tid)
      e env `(set!-ctx ,x ,env ,ctx)))
    ((lambda (,var) ,body)
     ((apply-ctx tid)
      ctx (make-closure var body env)))
    ((let ,x ,e ,body)
     ((interp tid)
      e env
      `(let-ctx ,x ,body ,env ,ctx)))
    ((letrec ,binding* ,body)
     (let-values (((f* x* fbody*) (decompose binding*)))
       ((interp tid)
        body (extend-env-rec f* x* fbody* env) ctx)))
    ((begin ,e)
     ((interp tid) e env ctx))
    ((begin ,e . ,e*)
     ((interp tid)
      e env `(begin-ctx ,e* ,env ,ctx)))
    ((print ,e)
     ((interp tid) e env `(print-ctx ,ctx)))
    ((spawn ,e)
     ((interp tid) e env `(spawn-ctx ,ctx)))
    ((,op ,e1 ,e2)
     (guard (memq op '(+ - * = eq?)))
     ((interp tid)
      e1 env
      `(op-ctx-1 ,op ,e2 ,env ,ctx)))
    ((,rator ,rand)
     ((interp tid)
      rator env
      `(rator-ctx ,rand ,env ,ctx)))))
(define run
  (case-lambda
    ((exp)
     (global-scheduler 'initialize)
     ((interp (new-thread-id))
      exp (empty-env) '(end-main-thread-ctx)))
    ((ticks exp)
     (global-scheduler 'set-ticks ticks)
     (global-scheduler 'initialize)
     ((interp (new-thread-id))
      exp (empty-env) '(end-main-thread-ctx)))))

练习5.55. 这里的答案基于笔者之前写下的练习5.53的答案, 以下未出现的定义就是和5.53保持一致.

(define (make-scheduler name ticks)
  (define queue (void))
  (define time (void))
  (define value (void))
  (define mailboxs (void))
  (define (add-thread thread)
    (enqueue! queue thread))
  (define (run-next-thread)
    (if (queue-empty? queue)
        (printf "The final answer is ~s." value)
        (let ((thread (dequeue! queue)))
          (set! time ticks)
          (thread))))
  (define (expired?)
    (zero? time))
  (define (count-down)
    (set! time (- time 1)))
  (define (set-answer answer)
    (set! value answer))
  (define (initialize)
    (set! queue (make-queue))
    (set! time ticks)
    (set! value (void))
    (set! mailboxs (make-hash)))
  (define (set-ticks new-ticks)
    (set! ticks new-ticks))
  (define (new-mailbox tid)
    (when (hash-ref-key mailboxs tid (lambda () #f))
      (error name "new-mailbox error: ~s" tid))
    (hash-set! mailboxs tid (make-queue)))
  (define (remove-mailbox tid)
    (hash-remove! mailboxs tid))
  (define (send-message tid msg)
    (let ((q (hash-ref mailboxs tid (lambda () #f))))
      (if q
          (begin (enqueue! q msg) #t)
          #f)))
  (define (receive-message tid env ctx)
    (define q (hash-ref mailboxs tid))
    (if (queue-empty? q)
        (begin
          (add-thread
           (lambda ()
             ((interp tid) '(receive) env ctx)))
          (run-next-thread))
        ((apply-ctx tid) ctx (dequeue! q))))
  (lambda (msg . arg*)
    (case msg
      ;I use apply to make sure the number of arguments
      ;matches the arity of the operation.
      ((add-thread) (apply add-thread arg*))
      ((run-next-thread) (apply run-next-thread arg*))
      ((expired?) (apply expired? arg*))
      ((count-down) (apply count-down arg*))
      ((send-message) (apply send-message arg*))
      ((receive-message) (apply receive-message arg*))
      ((new-mailbox) (apply new-mailbox arg*))
      ((remove-mailbox) (apply remove-mailbox arg*))
      ((set-answer) (apply set-answer arg*))
      ((initialize) (apply initialize arg*))
      ((set-ticks) (apply set-ticks arg*))
      (else (error name "unknown message ~s" msg)))))
(define ((apply-ctx tid) ctx val)
  (cond ((global-scheduler 'expired?)
         (global-scheduler
          'add-thread
          (lambda ()
            ((apply-ctx tid) ctx val)))
         (global-scheduler
          'run-next-thread))
        (else
         (global-scheduler 'count-down)
         (match ctx
           ((wait-ctx ,ctx)
            (wait val
                  (lambda ()
                    ((apply-ctx tid) ctx (void)))))
           ((signal-ctx ,ctx)
            (signal val
                    (lambda ()
                      ((apply-ctx tid) ctx (void)))))
           ((send-ctx-1 ,e2 ,env ,ctx)
            ((interp tid)
             e2 env `(send-ctx-2 ,val ,ctx)))
           ((send-ctx-2 ,tid0 ,ctx)
            (define flag
              (global-scheduler
               'send-message tid0 val))
            ((apply-ctx tid) ctx flag))
           ((if-ctx ,e2 ,e3 ,env ,ctx)
            (if val
                ((interp tid) e2 env ctx)
                ((interp tid) e3 env ctx)))
           ((set!-ctx ,x ,env ,ctx)
            (let ((ref (apply-env env x)))
              (setref ref val)
              ((apply-ctx tid) ctx (void))))
           ((let-ctx ,x ,body ,env ,ctx)
            ((interp tid)
             body
             (extend-env x (newref val) env)
             ctx))
           ((begin-ctx ,e* ,env ,ctx)
            ((interp tid) `(begin . ,e*) env ctx))
           ((print-ctx ,ctx)
            (printf "~s\n" val)
            ((apply-ctx tid) ctx (void)))
           ((spawn-ctx ,ctx)
            (define new-tid (new-thread-id))
            (global-scheduler
             'new-mailbox new-tid)
            (global-scheduler
             'add-thread
             (lambda ()
               ((apply-closure new-tid)
                val (newref tid)
                '(end-subthread-ctx))))
            ((apply-ctx tid) ctx new-tid))
           ((op-ctx-1 ,op ,e2 ,env ,ctx)
            ((interp tid)
             e2 env
             `(op-ctx-2 ,op ,val ,ctx)))
           ((op-ctx-2 ,op ,v1 ,ctx)
            (let ((v (case op
                       ((+) (+ v1 val))
                       ((-) (- v1 val))
                       ((*) (* v1 val))
                       ((=) (= v1 val))
                       ((eq?) (eq? v1 val)))))
              ((apply-ctx tid) ctx v)))
           ((rator-ctx ,rand ,env ,ctx)
            ((interp tid)
             rand env
             `(rand-ctx ,val ,ctx)))
           ((rand-ctx ,closure ,ctx)
            ((apply-closure tid)
             closure (newref val) ctx))
           ((end-main-thread-ctx)
            (global-scheduler
             'set-answer val)
            (global-scheduler
             'remove-mailbox tid)
            (global-scheduler
             'run-next-thread))
           ((end-subthread-ctx)
            (global-scheduler
             'remove-mailbox tid)
            (global-scheduler
             'run-next-thread))))))
(define ((interp tid) exp env ctx)
  (match exp
    (,int
     (guard (integer? int))
     ((apply-ctx tid) ctx int))
    (,bool
     (guard (boolean? bool))
     ((apply-ctx tid) ctx bool))
    ((quote ,sym)
     (guard (symbol? sym))
     ((apply-ctx tid) ctx sym))
    ((void)
     ((apply-ctx tid) ctx (void)))
    (,var
     (guard (symbol? var))
     ((apply-ctx tid)
      ctx (deref (apply-env env var))))
    ((mutex)
     ((apply-ctx tid) ctx (build-mutex)))
    ((wait ,e)
     ((interp tid) e env `(wait-ctx ,ctx)))
    ((signal ,e)
     ((interp tid) e env `(signal-ctx ,ctx)))
    ((send ,e1 ,e2)
     ((interp tid) e1 env `(send-ctx-1 ,e2 ,env ,ctx)))
    ((receive)
     (global-scheduler
      'receive-message
      tid env ctx))
    ((if ,e1 ,e2 ,e3)
     ((interp tid)
      e1 env
      `(if-ctx ,e2 ,e3 ,env ,ctx)))
    ((set! ,x ,e)
     ((interp tid)
      e env `(set!-ctx ,x ,env ,ctx)))
    ((lambda (,var) ,body)
     ((apply-ctx tid)
      ctx (make-closure var body env)))
    ((let ,x ,e ,body)
     ((interp tid)
      e env
      `(let-ctx ,x ,body ,env ,ctx)))
    ((letrec ,binding* ,body)
     (let-values (((f* x* fbody*) (decompose binding*)))
       ((interp tid)
        body (extend-env-rec f* x* fbody* env) ctx)))
    ((begin ,e)
     ((interp tid) e env ctx))
    ((begin ,e . ,e*)
     ((interp tid)
      e env `(begin-ctx ,e* ,env ,ctx)))
    ((print ,e)
     ((interp tid) e env `(print-ctx ,ctx)))
    ((spawn ,e)
     ((interp tid) e env `(spawn-ctx ,ctx)))
    ((,op ,e1 ,e2)
     (guard (memq op '(+ - * = eq?)))
     ((interp tid)
      e1 env
      `(op-ctx-1 ,op ,e2 ,env ,ctx)))
    ((,rator ,rand)
     ((interp tid)
      rator env
      `(rator-ctx ,rand ,env ,ctx)))))
(define run
  (case-lambda
    ((exp)
     (global-scheduler 'initialize)
     (let ((main-thread-id (new-thread-id)))
       (global-scheduler
        'new-mailbox main-thread-id)
       ((interp main-thread-id)
        exp (empty-env) '(end-main-thread-ctx))))
    ((ticks exp)
     (global-scheduler 'set-ticks ticks)
     (global-scheduler 'initialize)
     (let ((main-thread-id (new-thread-id)))
       (global-scheduler
        'new-mailbox main-thread-id)
       ((interp main-thread-id)
        exp (empty-env) '(end-main-thread-ctx))))))
且看一个简单的例子.
> (run
   '(letrec ((even? (lambda (parent-id)
                      (let n (receive)
                        (if (= n 0)
                            (send parent-id #t)
                            (let odd?-id (spawn odd?)
                              (begin
                                (send odd?-id (- n 1))
                                (send parent-id (receive))))))))
             (odd? (lambda (parent-id)
                     (let n (receive)
                       (if (= n 0)
                           (send parent-id #f)
                           (let even?-id (spawn even?)
                             (begin
                               (send even?-id (- n 1))
                               (send parent-id (receive)))))))))
      (let even?-id (spawn even?)
        (begin
          (send even?-id 16)
          (receive)))))
The final answer is #t.

第6章 延续传递风格

本章围绕着如何进行延续传递风格变换进行.

第7章 类型

介绍了类型, 将类型视为一种静态预测, 即良类型的程序在运行时不会出现类型错误. 之后介绍了最基本的类型检查还有类型推导 (通过合一) 的想法.

第8章 模块

第9章 对象和类