附录 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的标准库中找到,通过以下方式显式地加载。

  1. (require (lib "defmacro.ss")) ;provides define-macro
  2. (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)中即可:

  1. (define file-or-directory-modify-seconds
  2. (lambda (f)
  3. (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脚本的初始行差不多应该是:

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

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

用Gauche编写的Shell脚本以:

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

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

用SCM编写的Shell脚本以:

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

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

STk的Shell脚本以:

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

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

A.3 define-macro

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

  1. (define-macro MACRO-NAME
  2. (lambda MACRO-ARGS
  3. MACRO-BODY ...))

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

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

在老版本的MIT Scheme中:

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

在SCM和Kawa中:

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

在STk中:

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

A.4 load-relative

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

  1. (define load-relative
  2. (lambda (f)
  3. (let* ((n (string-length f))
  4. (full-pathname?
  5. (and (> n 0)
  6. (let ((c0 (string-ref f 0)))
  7. (or (char=? c0 #\/)
  8. (char=? c0 #\~))))))
  9. (basic-load
  10. (if full-pathname? f
  11. (let ((clp (current-load-port)))
  12. (if clp
  13. (string-append
  14. (dirname (port-filename clp)) "/" f)
  15. f)))))))

在SCM中可以这样写:

  1. (define load-relative
  2. (lambda (f)
  3. (let* ((n (string-length f))
  4. (full-pathname?
  5. (and (> n 0)
  6. (let ((c0 (string-ref f 0)))
  7. (or (char=? c0 #\/)
  8. (char=? c0 #\~))))))
  9. (load (if (and *load-pathname* full-pathname?)
  10. (in-vicinity (program-vicinity) f)
  11. f)))))

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

  1. (define *load-pathname* #f)
  2. (define stk%load load)
  3. (define load-relative
  4. (lambda (f)
  5. (fluid-let ((*load-pathname*
  6. (if (not *load-pathname*) f
  7. (let* ((n (string-length f))
  8. (full-pathname?
  9. (and (> n 0)
  10. (let ((c0 (string-ref f 0)))
  11. (or (char=? c0 #\/)
  12. (char=? c0 #\~))))))
  13. (if full-pathname? f
  14. (string-append
  15. (dirname *load-pathname*)
  16. "/" f))))))
  17. (stk%load *load-pathname*))))
  18. (define load
  19. (lambda (f)
  20. (error "Don't use load. Use load-relative instead.")))

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