附录 A Scheme方言

所有主要的Scheme方言都实现了R5RS规范。如果只使用R5RS中规定的功能,我们就能写出在这些方言中都能正常运行的代码。然而R5RS可能是为了更好的统一性,或是由于不可避免的系统依赖,在一些通用编程中无法忽略的重要问题上没有给出标准。因此这些Scheme方言不得不用一种特殊的非标准手段来解决这些问题。

本书使用了Scheme的MzScheme方言,因此也使用了一些非标准的特性。以下是本书中所有非标准的、依赖于MzScheme提供的特性:

  • 命令行(包括打开一个侦听会话以及Shell脚本)
  • define-macro
  • delete-file
  • file-exists?
  • file‑or‑directory‑modify‑seconds
  • fluid‑let
  • gensym
  • getenv
  • get‑output‑string
  • load‑relative
  • open‑input‑string
  • open‑output‑string
  • read‑line
  • reverse!
  • system
  • unless
  • when

以上这些命令中除了define-macrosystem外都是在MzScheme的默认环境中就有的。而这两个缺少的则可以在MzScheme的标准库中找到,通过以下方式显式地加载。

(require (lib "defmacro.ss")) ;provides define-macro
(require (lib "process.ss"))  ;provides system

另外还可以把这两段代码放在MzScheme的初始化文件中(在Unix系统下是用户家目录下的.mzschemerc文件)。

一些非标准的特性(如file-exists?delete-file)事实上在很多Scheme实现中已经是“标准”的特性了。另一些特性(如whenunless)或多或少有种“插件”式的定义(在本书中给出),因此可以在任何不具备这些过程的Scheme中加载。其他的需要针对每种方言来定义(如load-relative)。

本章描述了如何给你使用的Scheme方言加上本书中用到的这些非标准特性。想要了解更多关于你使用的Scheme方言,请参考其实现者提供的文档(附录E)。

A.1 调用和初始化文件

很多Scheme方言就像MzScheme一样都会从用户的家目录中载入初始化文件。我们可以把非标准功能的定义都放到这个初始化文件中,这样非常方便。比如,非标准过程file-or-directory-modify-seconds可以添加到Guile语言中,只要把下面的代码放到Guile的初始化文件(~/.guile)中即可:

(define file-or-directory-modify-seconds
  (lambda (f)
    (vector-ref (stat f) 9)))

另外,不同的Scheme方言有他们自己的不同的命令来启动对应的侦听器。下面的表格列出了不同Scheme方言对应的启动命令和初始化文件位置:

Dialect name Command Init file
Bigloo bigloo ~/.bigloorc
Chicken csi ~/.csirc
Gambit gsi ~/gambc.scm
Gauche gosh ~/.gaucherc
Guile guile ~/.guile
Kawa kawa ~/.kawarc.scm
MIT Scheme (Unix) scheme ~/.scheme.init
MIT Scheme (Win) scheme ~/scheme.ini
MzScheme (Unix, Mac OS X) mzscheme ~/.mzschemerc
MzScheme (Win, Mac OS Classic) mzscheme ~/mzschemerc.ss
SCM scm ~/ScmInit.scm
STk snow ~/.stkrc

A.2 Shell脚本

使用Guile编写的Shell脚本的初始行差不多应该是:

":";exec guile -s $0 "$@"

在Guile脚本中,调用过程(command-line)会以列表的形式返回脚本的名称和参数。如果只需要参数,只需要获得列表的cdr部分即可。

用Gauche编写的Shell脚本以:

":"; exec gosh -- $0 "$@"

开头。在脚本中变量*argv*中保存着脚本的参数列表。

用SCM编写的Shell脚本以:

":";exec scm -l $0 "$@"

开头。脚本中变量*argv*保存着一个列表,列表中包括Scheme可执行文件的名称,脚本的名称,-l这个选项,还有脚本的参数。如果只需要参数,对列表执行cdddr即可。

STk的Shell脚本以:

":";exec snow -f $0 "$@"

开头。在脚本中变量*argv*中保存着脚本的参数列表。

A.3 define-macro

本文中使用的define-macro宏在Scheme的很多方言如Bigloo,Chicken,Gambit,Gauche,Guile,MzScheme和Pocket中都有定义。在其他Scheme方言中定义宏的方式基本上是相同的。本节将指出其他Scheme方言是如何表示如下一段代码片段的:

(define-macro MACRO-NAME
  (lambda MACRO-ARGS
    MACRO-BODY ...))

在MIT Scheme第7.7.1或更高版本中,上述代码被写为:

(define-syntax MACRO-NAME
  (rsc-macro-transformer
    (let ((xfmr (lambda MACRO-ARGS MACRO-BODY ...)))
      (lambda (e r)
        (apply xfmr (cdr e))))))

在老版本的MIT Scheme中:

(syntax-table-define system-global-syntax-table 'MACRO-NAME
  (macro MACRO-ARGS
    MACRO-BODY ...))

在SCM和Kawa中:

(defmacro MACRO-NAME MACRO-ARGS
  MACRO-BODY ...)

在STk中:

(define-macro (MACRO-NAME . MACRO-ARGS)
  MACRO-BODY ...)

A.4 load-relative

过程load-relative可以在Guile中如下定义:

(define load-relative
  (lambda (f)
    (let* ((n (string-length f))
           (full-pathname?
             (and (> n 0)
                  (let ((c0 (string-ref f 0)))
                    (or (char=? c0 #\/)
                        (char=? c0 #\~))))))
      (basic-load
        (if full-pathname? f
            (let ((clp (current-load-port)))
              (if clp
                  (string-append
                    (dirname (port-filename clp)) "/" f)
                  f)))))))

在SCM中可以这样写:

(define load-relative
  (lambda (f)
    (let* ((n (string-length f))
           (full-pathname?
            (and (> n 0)
                 (let ((c0 (string-ref f 0)))
                   (or (char=? c0 #\/)
                       (char=? c0 #\~))))))
    (load (if (and *load-pathname* full-pathname?)
              (in-vicinity (program-vicinity) f)
              f)))))

对于STk,下面的load-relative过程仅在没有使用load过程时生效:

(define *load-pathname* #f)

(define stk%load load)

(define load-relative
  (lambda (f)
    (fluid-let ((*load-pathname*
                  (if (not *load-pathname*) f
                      (let* ((n (string-length f))
                             (full-pathname?
                               (and (> n 0)
                                    (let ((c0 (string-ref f 0)))
                                      (or (char=? c0 #\/)
                                          (char=? c0 #\~))))))
                        (if full-pathname? f
                            (string-append
                              (dirname *load-pathname*)
                              "/" f))))))
      (stk%load *load-pathname*))))

(define load
  (lambda (f)
    (error "Don't use load.  Use load-relative instead.")))

我们使用~/filename表示用户家目录中被调用的文件。