Scheme の方言

Scheme のメージャな方言はどれも、R5RS の仕様[cite{r5rs}] を 実装しています。R5RS に記述されている機能だけを使って、こうした 方言間でポータブルな Scheme のコードを書くことができます。 しかし、R5RS はコンセンサスの欠如や回避できないシステム依存性 のために自明でないプログラミングで無視できない、いくつかの問題に ついては沈黙しています。それ故、さまざまな方言が、非標準で、固有の 方法でこれらの問題に対応しています。

この文書では、MzScheme [cite{mzscheme}] という Scheme の方言を 使っています。それにともなって、非標準の機能もいくつか使用しています。 この文書で使われている、方言依存の機能の完全なリストは、 コマンドライン(リスナーセッションの開始とシェルスクリプトの両方)、 define-macrodelete-filefile-exists?file-or-directory-modify-secondsfluid-letgensymgetenvget-output-stringload-relativeopen-input-stringopen-output-stringread-linereverse!systemunlesswhen です。

これらのうち二つをのぞけば、どれも MzScheme のデフォルトの環境で あるものです。ないもの二つというのは、define-macro および system で、MzScheme の標準ライブラリで提供されています。これらは 以下のフォームを使って、明示的にMzSchemeにロードすることができます。

(require (lib "defmacro.ss")) ;define-macro を提供
(require (lib "process.ss"))  ;system を提供

これらのフォームを置く良い場所は、MzScheme の初期化ファイル (あるいはinit ファイル)で、Unix ではユーザのホーム ディレクトリの .maschemerc ファイルです。1

非標準機能のうちのいくつか(たとえば、file-exists?delete-file) は実際にはデファクトスタンダードで多くの Scheme で提供されています。 それ以外の機能のいくつか(たとえば、whenunless)は、 早晩、それらをプリミティブとしていない Scheme 方言のどれにでも、 ロード可能な「プラグイン」定義(この文書で与えた)があります。 のこりは、方言特有の定義があります(たとえば、load-relative)。

本章では、お使いの Scheme 方言に、この文書で使っている、非標準の機能 を組み込む方法を解説します。お使いの Scheme 方言について、さらに詳しくは、 実装者(付録 E)が提供している文書にあたってください。

A.1  起動と init ファイル

MzScheme と同様に、多くの Scheme 方言は、可能であれば、init ファイルを ロードします。この init ファイルは通常ユーザのホームディレクトリに 置かれます。この init ファイルは非標準の機能の定義を置く場所として 便利です。たとえば、非標準手続き file-or-directory-modify-seconds を Scheme の Guile [cite{guile}] 方言に追加するには、以下のコードを Guile の init ファイル ~/.guile に置いておけば可能です。

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

また、いろいろな Scheme 方言はそれぞれの別の名前のリスナー起動 コマンドを持っています。以下の表は、いくつかの Scheme 方言の 起動コマンドと init ファイルです。

        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  シェルスクリプト

Guile で書いたシェルスクリプトの最初の一行は以下のようになります。

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

このスクリプトでは、手続き呼び出し (command-line) は スクリプトの名前と引数のリストを返します。引数にだけアクセス したいのであれば、このリストの cdr をとればいいだけです。

Gauche [cite{gauche}] のシェルスクリプトの最初はこんな風になります。

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

このスクリプトでは、変数 *argv* がスクリプトへの引数のリストを 保持しています。

SCM で書いたシェルスクリプトはこんな感じです。

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

このスクリプトでは、変数 *argv* に Scheme の実行ファイル名、 スクリプトの名前、オプション -l、それにスクリプトへの引数が 含まれています。引数にだけアクセスしたいのであれば、このリストの cdddr をとればいいです。

STk [cite{stk}] のシェルスクリプトの最初は以下のとおり。

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

このスクリプトでは、変数 *argv* がスクリプトへの引数のリストを 保持しています。

A.3  define-macro

このテキストでは使われている define-macro は Scheme の方言 Bigloo [cite{bigloo}]、Chicken [cite{chicken}]、Gambit[cite{gambit}]、 Gauche [cite{gauche}]、Guile、MzScheme および Pocket Scheme [cite{pocketscheme}] にあらわれます。

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

MIT Scheme [cite{mitscheme}] version 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 [cite{scm}] および Kawa [cite{kawa}] では以下です。

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

STk [cite{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.")))


1 ~/filename という 記法をユーザのホームディレクトリにある filename というファイルを あらわすのに使います。