[Contents]   [Back]   [Prev]   [Up]   [Next]   [Forward]  


7. 標準手続き

本章ではSchemeの組み込み手続きを説明する。Schemeの初期環境(もしくは"トップレベル")は、有用な値を含む記憶域に一定数の変数をバインドして開始する。変数の大部分はデータを処理するプリミティブ手続きである。例えば変数 absは、引数を一つ取って数の絶対値を計算する手続き(が入るように初期化された記憶域)にバインドされている。変数+は和を計算する手続きにバインドされている。別の組み込み手続きに置き換えて容易に書くことができる組み込み手続きは、"ライブラリ手続き"と呼んで区別している。

プログラムは、トップレベルの定義を使用してあらゆる変数をバインドできる。ついで割り当てによって(section 5.1.6 割り当て参照)、そのバインディングを変更できる。Schemeの組み込み手続きの動作は、このような操作によっては変更されない。定義によって導入されなかったトップレベルバインディングを変更した場合、それによる組み込み手続きへの影響は不定である。

7.1 同値を調べる述語関数

述語関数は、必ず論理値(#tか#f)を返す手続きである。同値を調べる述語関数は、(対称的、反射的、推移的な)数学的同値関係の演算上の類同語である。本項で説明する同値述語関数の内で、eq?がもっとも緻密もしくは差別が厳しくequal?がもっともきめが粗い。eqv?eq?と比べるとやや差別がゆるい。

[[手続き]] (eqv? obj1 obj2)

eqv?手続きは、オブジェクトに関して利用価値の高い同値関係を定義するものである。簡単にいえばこの手続きは、obj1とobj2が通常は当然同一オブジェクトと見なされる場合に、#tを返す。この関係は多少解釈の余地を残してはいるが、以下に述べるeqv?の部分的仕様は、Schemeのすべての処理系に共通する。

eqv?手続きは次の場合に#tを返す。

eqv?手続きは次の場合に#fを返す。

次の例は、上記の規則ではeqv?の動作を充分には指定しきれない場合を示すものである。このような場合にいえることは、eqv?の返す値が論理値でなければならないということだけである。

  (eqv? "" "")             =>  unspecified
  (eqv? '#() '#())         =>  unspecified
  (eqv? (lambda (x) x)
        (lambda (x) x))    =>  unspecified
  (eqv? (lambda (x) x)
        (lambda (y) y))    =>  unspecified

局所状態を持つ手続きにeqv?を使用した場合を次の例に示す。gen-counterは、呼び出されるごとに異なる手続きを必ず返す。呼び出された手続きはそれぞれ固有の内部カウンタを持っているからである。一方gen-loserは、何度呼び出されてもすべて同値の手続きを返す。手続きの値もしくは副作用が、局所状態によって変化しないからである。(23)

  (define gen-counter
    (lambda ()
      (let ((n 0))
        (lambda () (set! n (+ n 1)) n))))
  (let ((g (gen-counter)))
    (eqv? g g))           =>  #t
  (eqv? (gen-counter) (gen-counter))
                          =>  #f
  (define gen-loser
    (lambda ()
      (let ((n 0))
        (lambda () (set! n (+ n 1)) 27))))
  (let ((g (gen-loser)))
    (eqv? g g))           =>  #t
  (eqv? (gen-loser) (gen-loser))
                          =>  unspecified

  (letrec ((f (lambda () (if (eqv? f g) 'both 'f)))
           (g (lambda () (if (eqv? f g) 'both 'g))))
    (eqv? f g))
                          =>  unspecified

  (letrec ((f (lambda () (if (eqv? f g) 'f 'both)))
           (g (lambda () (if (eqv? f g) 'g 'both))))
    (eqv? f g))
                          =>  #f

定数オブジェクト(リテラル式が返すもの)を変更した場合はエラーなので、必要ならば定数間で構造を共有することが処理系には許可されている。ただしこれは必須事項ではない。したがって定数に関するeqv?の値は、処理系によって異なる場合がある。

  (eqv? '(a) '(a))                 =>  unspecified
  (eqv? "a" "a")                   =>  unspecified
  (eqv? '(b) (cdr '(a b)))         =>  unspecified
  (let ((x '(a)))
    (eqv? x x))                    =>  #t

理論的根拠:

eqv?の上記の定義により、次のように処理系ごとに手続きと リテラル式を取り扱う自由度が生まれる。処理系は二つの手続きや 二つのリテラル式が互いに等しいかどうかを検出することも検出し ないこともできる。したがって二つのオブジェクトを表す同一の ポインタもしくは同一のビットパターンを使用して、等価なオブ ジェクトの表現を一つに統合するかしないかを決定できる。

[[手続き]] (eq? obj1 obj2)

eq?eqv?に似ているが、場合によってeqv?が行なう検出よりも微妙な違いを区別できる点が異なる。

シンボル、論理値、空リスト、ペア、空でない文字列とベクタについて、eq?eqv?は同一の動作をすることが保証されている。数と文字に関するeq?の動作は処理系によって異なるが、必ず真か偽を返すことになっており、eq?が真を返す時にはeqv?も真を返す。空のベクタと空の文字列についても、eq?eqv?は異なる動作をする場合がある。

  (eq? 'a 'a)                     =>  #t
  (eq? '(a) '(a))                 =>  unspecified
  (eq? (list 'a) (list 'a))       =>  #f
  (eq? "a" "a")                   =>  unspecified
  (eq? "" "")                     =>  unspecified
  (eq? '() '())                   =>  #t
  (eq? 2 2)                       =>  unspecified
  (eq? #\A #\A)                   =>  unspecified
  (eq? car car)                   =>  #t
  (let ((n (+ 2 3)))
    (eq? n n))                    =>  unspecified
  (let ((x '(a)))
    (eq? x x))                    =>  #t
  (let ((x '#()))
    (eq? x x))                    =>  #t
  (let ((p (lambda (x) x)))
    (eq? p p))                    =>  #t

理論的根拠:

例えば決まり切った複雑な操作でなく単純にポインタを比較する ことによって、eq?eqv?よりも通常ははるかに 効率的に実装できる。その理由の一つとしては、二つの数の eqv?を一定の時間内に計算することが不可能な場合が あることが挙げられる。一方eq?をポインタの比較として 実装した場合、比較は必ず一定時間内に終了する。状態を持つ オブジェクトを実装する手続きを使用するアプリケーションでは、 eq?eqv?と同じように使用できる。この時 eq?にはeqv?と同じ制限が課せられるからである。

[[ライブラリ手続き]] (equal? obj1 obj2)

equal?は、数とシンボルのような異なるオブジェクトにeqv?を適用して、ペア、ベクタ、文字列の内容を再帰的に比較する。経験的に、表示が同一であればオブジェクトは一般にequal?である。引数が循環的データ構造の場合、equal?は終了できないことがある。

  (equal? 'a 'a)                  =>  #t
  (equal? '(a) '(a))              =>  #t
  (equal? '(a (b) c)
          '(a (b) c))             =>  #t
  (equal? "abc" "abc")            =>  #t
  (equal? 2 2)                    =>  #t
  (equal? (make-vector 5 'a)
          (make-vector 5 'a))     =>  #t
  (equal? (lambda (x) x)
          (lambda (y) y))  =>  unspecified

7.2 数

Lispコミュニティーは、伝統的に数値計算をないがしろにしてきた。Common Lispが現れるまで、MacLisp系以外には充分に考え尽くされた数値計算の構成戦略は存在せず、数値コードを効率的に実行する努力は払われていなかった。本報告書はCommon Lispコミュニティーが行なった優れた業績を認識し、その勧告の多くを受け入れている。本報告書はある意味で、Schemeの目的に沿うように、Common Lispの提案の単純化と一般化を行なっている。

数学的な数、そのモデル化を試みたSchemeの数、Schemeに数を実装するために使用された計算装置に適した数の表現、数を書き出すために使用された記法の、それぞれを区別することは重要である。本報告書では数学的な数とSchemeの数の両方を示す数の種類として複素数実数有理数整数を使用する。固定小数点と浮動小数点のような計算装置に適した表現は、fixnumflonumのような名前を使用して参照する。

7.2.1 数の種類

数は数学的に、下位の層が上位の層の部分集合であるような、複数の層を持つ 階層として組織できる。

例えば3は整数である。したがって3は有理数でもあり、実数でもあり、複素数でもある。3をモデルとしたSchemeの数についても同じことがいえる。Schemeの数の場合、以上の種類は述語関数number?complex?real?rational?integer?で定義される。

数の種類とコンピュータ内部の数の表現との間に、単純な関係は存在しない。Schemeのほとんどの処理系では、3の表現について少くとも二種類の異なる方法が用意されているが、いずれの表現も同一の整数を指し示すものである。

Schemeの数値処理では、数は可能な限りその内部表現から独立した、抽象データとしてとり扱われる。数はSchemeの処理系ごとにfixnum、flonum、場合によってはそれ以外の表現を使用できるが、単純なプログラムを書く不注意なプログラマにこれが見えるようであってはならない。

ただし完全に表現される数とそうではない数を区別する必要がある。例えばデータ構造の指標は完全にわかっているものでなければならない。これはある種の記号代数学系の多項式の係数がわかっていなければならないのに似ている。一方測定結果というものはその性質上不完全であって無理数を有理数で近似する場合があり、したがってこれは不完全な近似である。完全数が必要な場所で不完全数が使用されていることを明らかにするために、Schemeでは完全数と不完全数を明示的に区別している。この区別は数の種類の次元に直交する。

7.2.2 完全性

Schemeの数は完全であるか、不完全であるかのいずれかである。完全な定数として書かれたか、完全な演算のみを使用して完全数から導かれた場合、数は完全である。不完全な定数として書かれたか、不完全な成分を使用して導かれたか、もしくは不完全な演算を使用して導かれた場合、数は不完全である。したがって数の不完全性という特徴は伝染する。

不完全な中間結果を含まない計算によって二つの処理系が完全な結果を生成した場合、最終的に二つの計算結果は数学的に等価になる。不完全数を含む計算については浮動小数点演算などの近似法が使用される場合があるために、二つの計算結果が等価であるということは一般に正しくない。ただし処理系はその結果を、実用上数学的に限りなく等価に近付けなければならない。

引数が完全数の時は、+のような有理演算は結果として必ず完全数を生成しなければならない。演算の結果が完全数を生成できない場合、処理系は実装上の制限の侵害を報告するか、でなければ侵害を報告せずに強制的に不完全な値を計算して生成できる。section 7.2.3 実装上の制限参照。

引数に不完全数が与えられた時は、inexact->exactを除いて本節に述べる演算の結果は、一般に不完全数を生成しなければならない。ただし演算結果の値が引数の不完全性に影響されないことが証明できる場合は、演算は完全数を結果として返してもよい。例えばその他の引数が不完全数であったとしても、完全なゼロを乗じたあらゆる数は、結果として完全なゼロを生成することができる。

7.2.3 実装上の制限

Schemeの処理系がsection 7.2.1 数の種類に述べるすべての階層を実装する必要はないが、処理系の目的とScheme言語の精神の両方に沿って、一貫性のある階層を実装しなければならない。例えば処理系の数をすべて実数にしたとしても、それはそれで利用価値が高い。

処理系には本節の要件にしたがう限り、いかなる種類の数であってもその一部の範囲だけをサポートすることが許される。いかなる種類の完全数についても、サポートされる完全数の範囲は、同一種類の不完全数についてサポートされる範囲と異なってよい。例えばflonumを使用してすべての不完全実数を表現する処理系は、不完全実数の範囲をflonum形式の動的な範囲に限定して(したがって不完全整数と不完全有理数の範囲を限定して)、実用上無制限の範囲の完全整数と完全有理数をサポートすることができる。そのような処理系においては以上の限定に加えて、範囲の上限と下限に近付くにしたがって、表現可能な不完全整数と不完全有理数との間の空隙が非常に大きくなる場合がある。

Schemeの処理系は、リスト、ベクタ、文字列の指標に使用される可能性がある数、もしくはリスト、ベクタ、文字列の長さの計算から生ずる可能性がある数の全範囲に渡って、完全整数をサポートしなければならない。lengthvector-lengthstring-length手続きは完全整数を返さなければならない。指標として完全整数以外を使用した場合はエラーである。さらに指標範囲外でいかなる実装上の制限が適用されるとしても、完全整数構文で表現された指標範囲内のあらゆる整数定数は、実際に完全整数として読み込まれる。最後に、すべての引数が完全整数で、数学的に予期される結果が処理系内部で完全整数として表現できる場合は、次に挙げる手続きは必ず完全整数の結果を返す。

  +              -               *
  quotient       remainder       modulo
  max            min             abs
  numerator      denominator     gcd
  lcm            floor           ceiling
  truncate       round           rationalize
  expt

処理系には実用上無制限の大きさと精度の完全整数完全有理数のサポートと上記の手続き、および完全な引数が与えられた場合に必ず完全な結果を返す手続きの実装が望ましい。ただしこれは必須ではない。完全な引数が与えられた場合に上記手続きの一つが完全な結果を返すことができない場合は、処理系は実装上の制限の侵害を報告するか、侵害を報告せずに強制的に結果を不完全数に変換することができる。この強制変換により、後で誤差が生ずる場合がある。

不完全数については、処理系は浮動小数点とその他の近似表現戦略を使用することができる。本報告書ではflonum表現を使用する処理系に、IEEEの32ビットと64ビットの浮動小数点規格にしたがうように推奨する。またその他の表現を使用する処理系は、この浮動小数点規格[IEEE]を使用した場合に実現可能な精度に合致するか、その精度を越える必要がある。ただしこれらは必須ではない。

flonum表現を使用する処理系は特に、以下の規則にしたがわなければならない。flonumの結果は、演算に与えられたあらゆる不完全引数の表現に使用された精度と、少くとも同等の精度で表現しなければならない。sqrtのような潜在的に不完全な演算が完全な引数に適用された場合は、可能な限り必ず完全な答を生成することが望ましい(例えば完全な4の自乗根は完全な2である必要がある)。ただしこれは必須ではない。しかし(sqrtのような)不完全な結果を生成する演算が完全数に行なわれ、かつその結果がflonumで表現される場合は、利用できる最も精度の高いflonumを使用しなければならない。ただし結果がflonum以外の方法で表現される場合はその表現精度は、利用できる最も精度の高いflonum形式と少くとも同等でなければならない。

数についてSchemeでは広範囲の記法が許されているが、特定の処理系はその一部だけをサポートしてもよい。例えばすべての数が実数である処理系では複素数について、直交座標記法と極座標記法をサポートする必要はない。処理系が完全数として表現できない完全な数値定数に遭遇した場合は、実装上の制限の侵害を報告するか、侵害を報告せずにその定数を不完全数で表現することができる。

7.2.4 数値定数の構文

数に関する記法上の表現の構文則は、section 8.1.1 辞書的構造で説明する。数値定数においては、大文字と小文字は区別されない点に注意する必要がある。

数は基数接頭辞を使用して、二進数、八進数、十進数、十六進数で書くことができる。基数接頭辞は、#b(二進)、#o(八進)、#d(十進)(十進)、#x(十六進)である。基数が存在しない場合、数は十進表現であると仮定される。

数値定数は接頭辞を使用して、完全数か不完全数のいずれかを指定できる。この接頭辞は完全数については#e不完全数については#iである。完全性接頭辞は、使用する基数接頭辞の前に書いても後ろに書いてもよい。数の記法上の表現に完全性接頭辞が存在しない場合、数値定数は不完全数である場合と完全数である場合がある。小数点、指数もしくは数字の代わりに"#"が表現に含まれる場合、数は不完全数である。それ以外の場合、数は完全数である。

さまざまな精度の不完全数を使用する処理系においては、定数の精度の指定が有用な場合がある。精度を指定する場合は、不完全数の表現に関する精度を示す指数標識を、数値定数に書くことができる。文字sfdlで、それぞれshort(短精度)、single(単精度)、double(倍精度)、long(長精度)を指定する。(不完全数の内部表現が四種に満たない場合、この指定は利用できる精度にマップされる。例えば内部表現が二種類の処理系では、短精度と単精度、および長精度と倍精度をそれぞれ一つの表現にマップできる。)加えて指数標識eは、処理系のデフォルトの精度を指定する。デフォルトの精度は少くとも倍精度と同等のものとするが、処理系がユーザにデフォルトを設定させてもよい。

  3.14159265358979F0
         Round to single -- 3.141593
  0.6L0
         Extend to long -- .600000000000000

7.2.5 数値演算

数値ルーチンに渡す引数の型に関する制限の指定に使用される命名規約については、section 2.3.3 見出しの形式に要約を載せている。本節に使用する例では、完全数の記法を使用して書かれたあらゆる数値定数は、完全数として表現されると仮定している。例によっては、不完全数の記法を使用して書かれた数値定数は、精度を失わずに表現できると仮定しているものもある。不完全数値定数は、不完全数の表現にflonumを使用する処理系でおそらく正しくなるように選択された。

[[手続き]] (number? obj)
[[手続き]] (complex? obj)
[[手続き]] (real? obj)
[[手続き]] (rational? obj)
[[手続き]] (integer? obj)

上記の数値型述語関数は、数値以外のものを含むあらゆる種類の引数に適用できる。オブジェクトが名前を指定された型であれば#tを返し、それ以外の場合は#fを返す。一般に数についてのある型の述語関数が真であれば、その数についての上位の述語関数も真である。

したがって数についてのある型の述語関数が偽であれば、その数についての下位の述語関数も偽である。

zを不完全な複素数とすれば、(zero? (imag-part z))が真の場合にのみ(real? z)は真である。zが不完全な実数の場合は、(= x (round x))が真ならばその場合にのみ(integer? x)は真である。

  (complex? 3+4i)         =>  #t
  (complex? 3)            =>  #t
  (real? 3)               =>  #t
  (real? -2.5+0.0i)       =>  #t
  (real? #e1e10)          =>  #t
  (rational? 6/10)        =>  #t
  (rational? 6/3)         =>  #t
  (integer? 3+0i)         =>  #t
  (integer? 3.0)          =>  #t
  (integer? 8/4)          =>  #t

注: 不完全数に関しては誤差によって結果が変わる場合があるために、上記の型述語関数の振舞いは信頼できない。

注: 多くの処理系ではrational?手続きはreal?手続きと、complex?手続きはnumber?手続きと等しくなる。ただし特殊な処理系ではある種の無理数を正確に表現できたり、複素数以外のある種の数をサポートするように数体系を拡張する場合がある。

[[手続き]] (exact? z)
[[手続き]] inexact? z)

以上の数値述語関数は、量の完全性をテストするものである。Schemeのいかなる数も、この述語関数のいずれか一つが正確に真になる。

[[手続き]] (= z1 z2 z3 ...)
[[手続き]] (< x1 x2 x3 ...)
[[手続き]] (> x1 x2 x3 ...)
[[手続き]] (<= x1 x2 x3 ...)
[[手続き]] (>= x1 x2 x3 ...)

以上の手続きは引数がそれぞれ、等しいか、単調に増加するか、単調に減少するか、単調に減少しないか、単調に増加しない場合に#tを返す。

これらは推移的(24)でなければならない。

注: 従来のLispライクな言語に実装された上記の述語関数は、推移的ではない。

注: 上記述語関数を使用して不完全数を比較することはエラーではないが、小さな誤差で結果が変わる場合があるために、その結果は信頼できない。疑いがある時は数値アナリストに相談してほしい。

[[ライブラリ手続き]] (zero? z)
[[ライブラリ手続き]] (positive? x)
[[ライブラリ手続き]] (negative? x)
[[ライブラリ手続き]] (odd? n)
[[ライブラリ手続き]] (even? n)

以上の数値述語関数は、数が特定の属性を持つかどうかをテストして#tか#fを返す。上記の注参照。

[[ライブラリ手続き]] (max x1 x2 ...)
[[ライブラリ手続き]] (min x1 x2 ...)

以上の手続きは引数の中の最大値か最小値を返す。

    (max 3 4) => 4 ; 完全数
    (max 3.9 4) => 4.0 ; 不完全数%

注: 引数の中に不完全数が存在する場合は、(誤差が結果を変えるほど大きくないことを手続きが証明できない限り---これは特殊な処理系でのみ可能である)結果も不完全数になる。完全性が混在する数の比較にminもしくはmaxを使用して、かつ精度を失わずに不完全数として結果の値を表現することができない場合は、手続きは実装上の制限の侵害を報告することができる。

[[手続き]] (+ z1 ...)
[[手続き]] (* z1 ...)

上記手続きは引数の和もしくは積を返す。

  (+ 3 4)                 =>  7
  (+ 3)                   =>  3
  (+)                     =>  0
  (* 4)                   =>  4
  (*)                     =>  1
[[手続き]] (- z1 z2)
[[手続き]] (- z)
[[オプション手続き]] (- z1 z2 ...)
[[手続き]] (/ z1 z2)
[[手続き]] (/  z)
[[オプション手続き]] (/ z1 z2 ...)
  (- 3 4)                 =>  -1
  (- 3 4 5)               =>  -6
  (- 3)                   =>  -3
  (/ 3 4 5)               =>  3/20
  (/ 3)                   =>  1/3
[[ライブラリ手続き]] (abs x)

absは引数の大きさを返す。

  (abs -7)                =>  7
[[手続き]] (quotient n1 n2)
[[手続き]] (remainder n1 n2)
[[手続き]] (modulo n1 n2)

以上の手続きは整数論的な(もしくは整数の)除算を実装するものである。n2はゼロであってはならない。手続きは3つとも整数を返す。n1/n2が整数ならば次が成立する。

  (quotient n1 n2)   => n1/n2
  (remainder n1 n2)  => 0
  (modulo n1 n2)     => 0

n1/n2が整数でないならば次が成立する。

  (quotient n1 n2)   => n_q
  (remainder n1 n2)  => n_r
  (modulo n1 n2)     => n_m

ただしn_qはゼロ側に丸めたn1/n2。0 < |n_r| < |n2|、0 < |n_m| < |n2|とする。n_rとn_mはn1からn2の倍数だけ異なり、n_rの符号はn1に同じく、n_mの符号はn2に同じとする。

以上から整数n1とn2について、n2がゼロに等しくないとすれば、以下を導くことができる。

     (= n1 (+ (* n2 (quotient n1 n2))
           (remainder n1 n2)))
                                 =>  #t

ただし演算に関係するすべての数は完全数とする。

  (modulo 13 4)           =>  1
  (remainder 13 4)        =>  1

  (modulo -13 4)          =>  3
  (remainder -13 4)       =>  -1

  (modulo 13 -4)          =>  -3
  (remainder 13 -4)       =>  1

  (modulo -13 -4)         =>  -1
  (remainder -13 -4)      =>  -1

  (remainder -13 -4.0)    =>  -1.0  ; 不完全数
[[ライブラリ手続き]] (gcd n1 ...)
[[ライブラリ手続き]] (lcm n1 ...)

以上の手続きは、引数の最大公約数と最小公倍数を返す。結果は必ず非負の数である。

  (gcd 32 -36)            =>  4
  (gcd)                   =>  0
  (lcm 32 -36)            =>  288
  (lcm 32.0 -36)          =>  288.0  ; 不完全数
  (lcm)                   =>  1
[[手続き]] (numerator q)
[[手続き]] (denominator q)

以上の手続きは引数の分子もしくは分母を返す。結果は引数が規約分数であるかのように計算される。分母は常に正である。0の分母は1に定義される。

  (numerator (/ 6 4))         =>  3
  (denominator (/ 6 4))       =>  2
  (denominator
    (exact->inexact (/ 6 4))) => 2.0
[[手続き]] (floor x)
[[手続き]] (ceiling x)
[[手続き]] (truncate x)
[[手続き]] (round x)

以上の手続きは整数を返す。floorはxよりも大きくない最大の整数を返す。celingはxよりも小さくない最小の整数を返す。 truncateは絶対値がxの絶対値よりも大きくない、xに最も近い整数を返す。roundはxが二つの整数の中間にある場合でもそれを丸めて、xに最も近い整数を返す。

理論的根拠:

roundは、IEEE浮動小数点規格に指定されるデフォルトの 丸めモードと一貫性を保つように、数値を丸める。

注: 以上の手続きの引数が不完全数の場合は結果も不完全数である。完全数が必要な場合は、結果をinexact->exact手続きにかける必要がある。

  (floor -4.3)          =>  -5.0
  (ceiling -4.3)        =>  -4.0
  (truncate -4.3)       =>  -4.0
  (round -4.3)          =>  -4.0

  (floor 3.5)           =>  3.0
  (ceiling 3.5)         =>  4.0
  (truncate 3.5)        =>  3.0
  (round 3.5)           =>  4.0  ; 不完全数

  (round 7/2)           =>  4    ; 完全数
  (round 7)             =>  7
[[ライブラリ手続き]] (rationalize x y)

rationalizeは、y以上にはxと異ならない最も単純な有理数を返す。r1 = p1/q1かつr2 = p2/q2(規約分数)で、|p1| <= |p2|かつ|q1| <= |q2|ならば、有理数r1はもう一つの有理数r2よりも単純である。したがって3/5は4/7よりも単純である。すべての有理数がこの順にしたがうとは限らないが(2/7と3/5参照)、すべての区間には、その区間のその他すべての有理数よりも単純な有理数が一つ含まれる(2/7と3/5の間にはより単純な2/5が存在する)。0 = 0/1は、すべての有理数の中で最も単純であることに注意する必要がある。

  (rationalize
    (inexact->exact .3) 1/10)  => 1/3   ;完全数
  (rationalize .3 1/10)        => #i1/3 ;不完全数
[[手続き]] (exp z)
[[手続き]] (log z)
[[手続き]] (sin z)
[[手続き]] (cos z)
[[手続き]] (tan z)
[[手続き]] (asin z)
[[手続き]] (acos z)
[[手続き]] (atan z)
[[手続き]] (atan y x))

以上の手続きは、実数全般をサポートするすべての処理系に実装され、通常の超越関数を計算する。logは、zの(常用対数ではなく)自然対数を計算する。asinacosatanはそれぞれ、arcsine(sin^-1)、acrcosine(cos^-1)、arctangent(tan^-1)を計算する。引数を二つ取るatanの別形は、複素数全般をサポートしない処理系においても、(angle (make-rectangular x y))(下記参照)を計算する。

一般に数学関数log、arcsine、arccosine、arctangentは、多重定義である。log(z)の値は、虚部が-pi(-piを除く)からpi(piを含む)の範囲にあるものとして定義される。log(0)は無定義である。logをこのように定義した場合、sin^-1(z)、cos^-1(z)、tan^-1(z)の値は次の式の通りとなる。

  sin^-1(z) = -i*log(i*z + sqrt(1 - z^2))
  cos^-1(z) = pi/2 - sin^-1(z)
  tan^-1(z) = (log(1 + i*z) - log(1 - i*z)) / (2*i)

以上の仕様は[CLTL]にしたがうものであり、[CLTL]はさらに[PENFIELD81]を引用している。分岐線法、境界条件、および上記関数の実装の詳細な検討については、原典にあたっていただきたい。上記手続きは、可能な時は実数引数から実数の結果を生成する。

[[手続き]] (sqrt z)

zの主平方根を返す。結果は正の実部か、実部がゼロの非負の虚部になる。

[[手続き]] (expt z1 z2)

z1のz2乗を返す。z1 =/= 0ならば、

  z1^z2 = e^(z2*log(z1))

である。

0^zはz = 0ならば1、さもなければ0である。

[[手続き]] (make-rectangular x1 x2)
[[手続き]] (make-polar x3 x4)
[[手続き]] (real-part z)
[[手続き]] (imag-part z)
[[手続き]] (magnitude z)
[[手続き]] (angle z)

以上の手続きは、複素数全般をサポートするすべての処理系に実装される。x1、x2、x3、x4が実数、zが複素数で次を満足するとすれば、

  z = x1 + x2*i = x3 * e^(i*x4)
  (make-rectangular x1 x2) => z
  (make-polar x3 x4)     => z
  (real-part z)                  => x1
  (imag-part z)                  => x2
  (magnitude z)                  => |x3|
  (angle z)                      => x_angle

が返される。ただしある整数nに対してx_angle = x4 + 2*pi*nとして、-pi < x_angle <= piである。

理論的根拠:

magnitudeは実数引数に対してはabsと同じもの である。ただしabsはすべての処理系が実装しなければ ならないが、magnitudeは複素数全般をサポートする 処理系にのみ実装される。

[[手続き]] (exact->inexact z)
[[手続き]] (inexact->exact z)

exact->inexactはzの不完全数表現を返す。返される値は数値的に引数に最も近い不完全数である。完全数引数にに近い適当な等価な不完全数が存在しない場合は、実装上の制限の侵害を報告することができる。

inexact->exactはzの完全数表現を返す。返される値は数値的に引数に最も近い完全数である。不完全数引数にに近い適当な等価な完全数が存在しない場合は、実装上の制限の侵害を報告することができる。

以上の手続きは、処理系に応じた全領域にわたって、完全整数と不完全整数の自然な一対一対応を実装するものである。section 7.2.3 実装上の制限参照。

7.2.6 数値の入出力

[[手続き]] (number->string number)
[[手続き]] (number->string number radix)

radix(基数)は完全整数で、2、8、10、もしくは16でなければならない。省略した場合、基数はデフォルトの10に設定される。手続きnumber->stringは数と基数の引数をそれぞれ一つづつ取って。その数とその基数による外部表現を、次の式が真となるような文字列として返す。

  (let ((number number)
        (radix radix))
    (eqv? number
          (string->number (number->string number
                                          radix)
                          radix)))

上の式が真となる結果が存在しない場合はエラーである。

zが不完全数、基数が10で小数点を含む結果が上記の式を満足すれば、結果には小数点が含まれ、上記の式が真になる[HOWTOPRINT]、[HOWTOREAD]ために必要な最小の桁数を使用して結果が表現される(指数と後続する0を除く)。さもなければ結果の形式は不定である。

number->stringが返す結果に明示的に基数接頭辞が含まれること はない。

注: エラー状況はzが複素数でないか、複素数であってもその実部か虚部が有理数でない場合にだけ、発生できる。

理論的根拠:

zがflonumを使用して表現される不完全数で基数が10であれば、 結果に小数点を含めることによって上記の式は通常満足される。 無限大Nanと flonum表現については、結果を不定とする状況が 用意されている。

[[手続き]] (string->number string)
[[手続き]] (string->number string radix)

渡された文字列が表す数を、最大精度の表現で返す。基数radixは2、8、10、16のいずれかの完全数でなければならない。基数が与えられた場合はそれがデフォルトの基数となり、これは文字列内の基数接頭辞(例えば"#o177")で明示的に上書きすることができる。基数が与えられていない場合のデフォルトの基数は10である。数について文字列が文法的に有効な記法でない場合は、 string->numberは#fを返す。

  (string->number "100")        =>  100
  (string->number "100" 16)     =>  256
  (string->number "1e2")        =>  100.0
  (string->number "15##")       =>  1500.0

注: 処理系は次のように、string->numberの定義域を制限する場合がある。文字列に明示的な基数が含まれる時は、string->numberは常に#fを返すことができる。サポートするすべての数が実数である処理系では、複素数の記法として文字列に極形式記法か矩形形式記法が使用された時は、string->numberは常に#fを返すことができる。すべての数が整数の処理系では、記法に分数が使用された時は、string->numberは常に#fを返すことができる。すべての数が完全数の処理系では、指数標識もしくは明示的な完全性接頭辞が使用された時、もしくは数字の代わりに#が書かれた場合でも、 string->numberは常に#fを返すことができる。すべての不完全数が整数の処理系では、小数点が使用された時は、string->numberは常に#fを返すことができる。

7.3 その他のデータ型

本節ではSchemeの数値以外のデータ型、すなわち論理型、ペア型、リスト型、シンボル型、文字型、文字列型、ベクタ型のあるものについての演算を説明する。

7.3.1 論理式

真と偽に対する論理オブジェクトは#tと#fと書く。ただし実際に重要なものは、Schemeの条件式(ifcondandordo)が真または偽として処理するオブジェクトである。語句"真値"(単に"真"という場合もある)は、条件式が真として処理するあらゆるオブジェクトを意味する。語句"偽値"(単に"偽"という場合もある)は、条件式が偽として処理するあらゆるオブジェクトを意味する。

Schemeのすべての標準の値の内で、#fだけが条件式の中で偽と判断される。#fを除くSchemeのすべての標準の値は真と判断される。これには#t、ペア、空リスト、シンボル、数、文字列、ベクタ、手続きが含まれる。

注: Scheme以外のLisp方言に慣れているプログラマは、Schemeでは#fと空リストの両方とも、シンボルnilとは異なっていることに注意しなければならない。

論理定数はそれ自身に評価される。したがってプログラム内でクォートをつける必要はない。

  #t        =>  #t
  #f        =>  #f
  '#f       =>  #f
[[ライブラリ手続き]] (not  obj)

notはobjが偽であれば#tを返し、それ以外では#f を返す。

  (not #t)         =>  #f
  (not 3)          =>  #f
  (not (list 3))   =>  #f
  (not #f)         =>  #t
  (not '())        =>  #f
  (not (list))     =>  #f
  (not 'nil)       =>  #f
[[ライブラリ手続き]] (boolean?  obj)

boolean?はobjが#tか#fのいずれかであれば#tを返し、それ以外の場合は#fを返す。

  (boolean? #f)         =>  #t
  (boolean? 0)          =>  #f
  (boolean? '())        =>  #f

7.3.2 ペアとリスト

ペア(ドットペアと呼ばれることもある)は、car(カー)とcdr(クダー)と呼ばれる二つのフィールドを持つレコード構造である。carとcdrは歴史的にそう呼び慣わされている。ペアはcons手続きで作成される。carフィールドとcdrフィールドは、それぞれ手続きcarcdrで取り出される。carフィールドとcdrフィールドには、それぞれ手続きset-car!set-cdr!で代入が行なわれる。

ペアは主としてリストを表現するのに使用される。リストは空リストであるか、cdrがリストであるペアとして再帰的に定義できる。さらに厳密にいえば、リストの集合は次のような最小の集合Xとして定義される。

リストは連続するペアで構成され、各ペアのcarフィールドに含まれるオブジェクトがそのリストの要素である。例えば要素が二つのリストとは、リストの最初の要素であるcarとペアcdrで構成されるペアである。そのcdrは、carがリストの第2の要素であり、cdrのcdrが空のリストである。リストの長さは要素の数で、これはペアの数に等しい。

空のリストはこの型の中の特別のオブジェクトである(これはペアではない)。空のリストに要素はなく、その長さはゼロである。

注: 上記の定義は、リストがすべて有限の長さで、空のリストで終ることを意味している。

Schemeのペアのもっとも一般的な記法(外部表現)は"ドット付"記法(c1 . c2)である。ここにc1はcarフィールドの値、c2はcdrフィールドの値である。例えば(4 . 5)は、carが4でcdrが5のペアである。(4 . 5)はペアの外部表現であって、ペアに評価される式ではないことに注意しなければならない。

リストにはもっと簡素な記法が使用できる。リストの要素を単に空白で区切って小括弧で囲む。空のリストは()と書く。次に例を示す。

  (a b c d e)と
  (a . (b . (c . (d . (e . ())))))

はシンボルのリストについての等価な記法である。最後が空のリストでない一連のペアは、変則リストと呼ばれる。変則リストはリストではないことに注意しなければならない。リストとドット記法を組み合わせて、変則リストを書くことができる。

  (a b c . d)と
  (a . (b . (c . d))) 

とは等価である。

ペアがリストであるかどうかは、cdrフィールドに何が保管されているかによる。set-cdr!手続きを使用した時は、その瞬間リストであったものが次の瞬間にはリストでなくなる場合がある。

  (define x (list 'a 'b 'c))
  (define y x)
  y                       =>  (a b c)
  (list? y)               =>  #t
  (set-cdr! x 4)          =>  unspecified
  x                       =>  (a . 4)
  (eqv? x y)              =>  #t
  y                       =>  (a . 4)
  (list? y)               =>  #f
  (set-cdr! x x)          =>  unspecified
  (list? x)               =>  #f

read手続きが読み込むリテラル式とオブジェクトの外部表現の内部では、'<データ>、`<データ>、,<データ>、,@<データ>は2要素リストを指す。その第一要素はそれぞれシンボル quotequasiquoteunquoteunquote-splicingであり、いずれの場合もその第二要素は<データ>である。この規約は、任意のSchemeプログラムをリストで表現できるように用意したものである。Schemeの文法では、すべての<式>は<データ>でもある(section 8.1.2 外部表現参照)。数ある特徴の中でもこれによって、read手続きを使用してSchemeプログラムを解析することができる。section 4.3 外部表現参照。

[[手続き]] (pair? obj)

pair?はobjがペアであれば#tを返し、それ以外の場合は#fを返す。

  (pair? '(a . b))        =>  #t
  (pair? '(a b c))        =>  #t
  (pair? '())             =>  #f
  (pair? '#(a b))         =>  #f
[[手続き]] (cons obj1 obj2)

この手続きはペアを新しく割り当てて返し、そのcarはobj1、cdrはobj2である。このペアは、既存のすべてのオブジェクトとは(eqv?の意味で)異なることが保証されている。

  (cons 'a '())           =>  (a)
  (cons '(a) '(b c d))    =>  ((a) b c d)
  (cons "a" '(b c))       =>  ("a" b c)
  (cons 'a 3)             =>  (a . 3)
  (cons '(a b) 'c)        =>  ((a b) . c)
[[手続き]] (car pair)

この手続きはpairのcarフィールドの内容を返す。空リストのcarを取った場合はエラーであることに注意しなければならない。

  (car '(a b c))          =>  a
  (car '((a) b c d))      =>  (a)
  (car '(1 . 2))          =>  1
  (car '())               =>  error
[[手続き]] (cdr pair)

この手続きはpairのcdrフィールドの内容を返す。空リストのcdrを取った場合はエラーであることに注意しなければならない。

  (cdr '((a) b c d))      =>  (b c d)
  (cdr '(1 . 2))          =>  2
  (cdr '())               =>  error
[[手続き]] (set-car! pair obj)

この手続きはpairのcarフィールドにobjを保管する。set-car!が返す値は不定である。

  (define (f) (list 'not-a-constant-list))(25)
  (set-car! (f) 3) => unspecified
  (26)
  (set-car! (g) 3) => error
  (27)
[[手続き]] (set-cdr! pair obj)

この手続きはpairのcdrフィールドにobjを保管する。set-cdr!が返す値は不定である。

[[ライブラリ手続き]] (caar pair)
[[ライブラリ手続き]] (cadr pair)
    ...                   ...
[[ライブラリ手続き]] (cdddar pair)
[[ライブラリ手続き]] (cddddr pair)

以上の手続きはcarcdrの組合せである。例えばcaddrは次のように定義される。

  (define caddr (lambda (x) (car (cdr (cdr x)))))

深さ4までの任意の組合せが用意されており、このような手続きは合計28ある。

[[ライブラリ手続き]] (null? obj)

objが空リストの時に#tを返し、それ以外の場合は#fを返す。

[[ライブラリ手続き]] (list? obj)

objがリストの場合は#tを返し、それ以外の場合は#fを返す。定義上すべてのリストは長さが有限で、終端は空リストである。

 (list? '(a b c))     =>  #t
 (list? '())          =>  #t
 (list? '(a . b))     =>  #f
 (let ((x (list 'a)))
   (set-cdr! x x)
   (list? x))         =>  #f
[[ライブラリ手続き]] (list obj ...)

引数を新しく割り当てたリストにして返す。

  (list 'a (+ 3 4) 'c)            =>  (a 7 c)
  (list)                          =>  ()
[[ライブラリ手続き]] (length list)

リストlistの長さを返す。

  (length '(a b c))               =>  3
  (length '(a (b) (c d e)))       =>  3
  (length '())                    =>  0
[[ライブラリ手続き]] (append list ...)

最初のリストlistの要素にその他のリストの要素を続けたものを要素とするリストを返す。

  (append '(x) '(y))              =>  (x y)
  (append '(a) '(b c d))          =>  (a b c d)
  (append '(a (b)) '((c)))        =>  (a (b) (c))

結果のリストは、最後のリスト引数と構造を共有する場合を除いて、必ず新しく割り当てられる。最後のリストは実際にはいかなるオブジェクトでもよい。最後のリストが正しいリストでない場合は、変則リストが生成される。

  (append '(a b) '(c . d))        =>  (a b c . d)
  (append '() 'a)                 =>  a
[[ライブラリ手続き]] (reverse list)

listの要素を逆順にしたリストを新しく割り当てて返す。

  (reverse '(a b c))              =>  (c b a)
  (reverse '(a (b c) d (e (f))))  =>  ((e (f)) d (b c) a)
[[ライブラリ手続き]]  (list-tail list k)

listから最初のk個の要素を取り除いた部分リストを返す。listの要素数がk個より少い場合はエラーである。list-tailは次のように定義できる。

(define list-tail
  (lambda (x k)
    (if (zero? k)
        x 
        (list-tail (cdr x) (- k 1)))))
[[ライブラリ手続き]] (list-ref list k)

listのk番目の要素を返す。(これは(list-tail list k)のcarに等しい)。listの要素数がk個より少かった場合はエラーである。

  (list-ref '(a b c d) 2)                 =>  c
  (list-ref '(a b c d)
            (inexact->exact (round 1.8))) =>  c
[[ライブラリ手続き]] (memq obj list)
[[ライブラリ手続き]] (memv obj list)
[[ライブラリ手続き]] (member obj list)

以上の手続きは、carがobjであるlistの部分リストを返す。ただしこの部分リストは、kがlistの長さ未満の場合に(list-tail list k)が返す空でないリストとする。objがlistに存在しなかった場合は、(空リストでなく)#fが返される。objとlistとの比較にmemqeq?を使用するが、memveqv?を、memberequal?を使用する。

  (memq 'a '(a b c))              =>  (a b c)
  (memq 'b '(a b c))              =>  (b c)
  (memq 'a '(b c d))              =>  #f
  (memq (list 'a) '(b (a) c))     =>  #f
  (member (list 'a)
          '(b (a) c))             =>  ((a) c)
  (memq 101 '(100 101 102))       =>  unspecified
  (memv 101 '(100 101 102))       =>  (101 102)
[[ライブラリ手続き]] (assq obj alist)
[[ライブラリ手続き]] (assv obj alist)
[[ライブラリ手続き]] (assoc obj alist)

alist("association list" = 連想リスト)はペアのリストでなければならない。上記手続きはcarフィールドがobjである最初のペアを検索して、そのペアを返す。alistにcarがobjであるようなペアが存在しない場合は、(空リストではなく)#fが返される。各ペアのcarフィールドとobjとを比較するにあたって、assqeq?を使用する。一方 assveqv? を、assoceequal?を使用する。

  (define e '((a 1) (b 2) (c 3)))
  (assq 'a e)                =>  (a 1)
  (assq 'b e)                =>  (b 2)
  (assq 'd e)                =>  #f
  (assq (list 'a) '(((a)) ((b)) ((c))))
                             =>  #f
  (assoc (list 'a) '(((a)) ((b)) ((c))))
                             =>  ((a))
  (assq 5 '((2 3) (5 7) (11 13)))
                             =>  unspecified
  (assv 5 '((2 3) (5 7) (11 13)))
                             =>  (5 7)

理論的根拠:

memqmemvmemberassqassvassocは通常述語関数として使用される ものであるが、名前に疑問符が含まれていない。これらは単に #tか#fを返すのではなく、利用価値のある値を返すからである。

7.3.3 シンボル

シンボルは、二つのシンボルの名前と綴が同一の場合にのみ、(eqv?の意味で)その二つのシンボルが同一である点に利用価値があるオブジェクトである。これはプログラム内で識別子を表すのに必要な特徴である。したがってSchemeのほとんどの処理系では、その目的のために内部的にシンボルを使用している。シンボルはこれ以外の多くの応用にも利用価値が高い。例えばPascalで使用される列挙型の値のように使用することができる。

シンボルの表現規則は、識別子の表現規則と正確に等しい。section 3.1 識別子とsection 8.1.1 辞書的構造参照。

リテラル式の一部として返されるか、もしくはread手続きを使用して読み込まれ、その後write手続きを使用して書き込まれたいかなるシンボルも、(eqv?の意味で)同一シンボルとしてもう一度読み込まれることが保証されている。ただしstring->symbol手続きでは、write/readのこの不変性が維持されないシンボルが作成される場合がある。この手続きで標準以外の文字を使用して作成された名前には、特殊文字が含まれるからである。

注: Schemeの処理系によっては、すべてのシンボルにwrite/readの不変性を保証するために、"スラッシュ化"(slashification)(28)として知られる機能を備えているものがある。ただし歴史的にはこの機能の重要な利用法の大部分は、文字列データ型の不備を補うことであった。

処理系によっては"未登録のシンボル"(29)を使用するものがあり、これはスラッシュ化を使用する処理系においてもwrite/readの不変性を破壊するものである。二つのシンボルの名前と綴が同一の場合にのみシンボルは等しいという規則にも例外が発生する。

[[手続き]] (symbol? obj)

objがシンボルの場合に#tを返し、それ以外の場合は#fを返す。

  (symbol? 'foo)          =>  #t
  (symbol? (car '(a b)))  =>  #t
  (symbol? "bar")         =>  #f
  (symbol? 'nil)          =>  #t
  (symbol? '())           =>  #f
  (symbol? #f)            =>  #f
[[手続き]] (symbol->string symbol)

シンボルsymbolの名前を文字列として返す。シンボルがリテラル式(section 5.1.2 リテラル式参照)の値として返されるオブジェクトの一部もしくはread手続きの呼び出しで返されるオブジェクトで、かつ名前にアルファベット文字が含まれる場合、この文字列には標準の文字が入る。標準の文字が大文字であるか小文字であるかは、処理系が決定する。処理系によって、小文字に統一される場合と大文字に統一される場合がある。シンボルがstring->symbolによって返される場合は、string->symbolに渡される文字が大文字であれば大文字が、小文字であれば小文字が返される。この手続きが返す文字列に、string-set!などの変更手続きを使用した場合はエラーである。

以下の例は、処理系の標準文字が小文字であると仮定した場合である。

  (symbol->string 'flying-fish)
                                    =>  "flying-fish"
  (symbol->string 'Martin)          =>  "martin"
  (symbol->string
     (string->symbol "Malvina"))
                                    =>  "Malvina"
[[手続き]] (string->symbol string)

名前が文字列stringであるシンボルを返す。この手続きは標準以外の文字集合の場合に名前に特殊文字が含まれるシンボルを作成できるが、そのようなシンボルを作成することは通常好ましい発想ではない。処理系によってはそのようなシンボルを読み込めないものがあるからである。symbol->string参照。

以下の例では標準文字が小文字であることを仮定している。

  (eq? 'mISSISSIppi 'mississippi)     =>  #t
  (string->symbol "mISSISSIppi")      =>  the symbol with name "mISSISSIppi"
  (eq? 'bitBlt (string->symbol "bitBlt"))
                                      =>  #f
  (eq? 'JollyWog
       (string->symbol
         (symbol->string 'JollyWog))) =>  #t
  (string=? "K. Harper, M.D."
            (symbol->string
              (string->symbol "K. Harper, M.D.")))
                                      =>  #t

7.3.4 文字型

文字型は、文字と数字などの印字文字を表すオブジェクトである。文字型は記法#\<文字もしくは#\<文字名>を使用して書かれる。例えば次の通りである。

  #\a       ; 小文字
  #\A       ; 大文字
  #\(       ; 左小括弧
  #\        ; スペース
  #\space   ; スペースを書く望ましい方法
  #\newline ; newline文字

大文字と小文字の別は#\<文字>の場合は意味を持つが、#\<文字名>では意味を持たない。#\<文字名>内の<文字>がアルファベットの場合は、<文字>に続く文字列はスペースや括弧などの区切り文字でなければならない。この規則により例えば文字列"#\space"が、スペース文字を表すのか、それとも文字を表す"#\s"にシンボル"pace"が後続したものか、いずれにも解釈できるといった曖昧さが避けられる。

#\記法で書かれた文字はそれ自身に評価されるので、プログラム内でクォート(')をつける必要はない。

文字を扱う手続きの中には大文字と小文字を区別しないものがある。大文字と小文字を無視する手続きは、名称の中に"-ci"("case insensitive")が入っている。

[[手続き]] (char? obj)

objが文字の場合には#tを返し、それ以外の場合には#fを返す。

[[手続き]] (char=? char1 char2)
[[手続き]] (char<? char1 char2)
[[手続き]] char>? char1 char2)
[[手続き]] (char<=? char1 char2)
[[手続き]] (char>=? char1 char2)

以上の手続きのためには文字集合に関する完全な順序付けが必要である。順序づけが完全に行なわれていれば以下が保証される。

処理系によっては以上の手続きを一般化して、対応する数値述語関数のように三つ以上の引数を取るようにしているものがある。

[[ライブラリ手続き]] (char-ci=? char1 char2)
[[ライブラリ手続き]] (char-ci<? char1 char2)
[[ライブラリ手続き]] (char-ci>? char1 char2)
[[ライブラリ手続き]] (char-ci<=? char1 char2)
[[ライブラリ手続き]] (char-ci>=? char1 char2)

以上の手続きはchar=?その他に似ているが、大文字と小文字を同一のものとして処理するものである。例えば(char-ci=? #\A #\a)は#tを返す。処理系によっては手続きを一般化して、対応する数値述語関数のように三つ以上の引数を取るようにしているものがある。

[[ライブラリ手続き]] (char-alphabetic? char)
[[ライブラリ手続き]] (char-numeric? char)
[[ライブラリ手続き]] (char-whitespace? char)
[[ライブラリ手続き]] (char-upper-case? letter)
[[ライブラリ手続き]] (char-lower-case? letter)

以上の手続きはそれぞれ、引数がアルファベット、数字、空白文字、大文字、小文字の場合に#tを返し、それ以外の場合は#fを返す。ASCII文字集合に固有の以下の注は、参考指針として述べるものである。アルファベット文字は大文字と小文字を合わせて52ある。数字は十進数で10ある。空白文字はスペース文字、タブ文字、改行文字、改ページ文字、キャリッジリターンである。

[[手続き]] (char->integer char)
[[手続き]] (integer->char n)

文字を渡されると、char->integerはその文字の完全数による整数表現を返す。char->integerのもとでの文字イメージである完全整数を渡されると、integer->charは該当する文字を返す。以上の手続きはchar<=?による順序づけのもとで文字集合間に、<=による順序づけのもとで整数の部分集合の一部に、それぞれ順序を保存した同型写像(30)を実装するものである。すなわち

  (char<=? a b) => #t  かつ  (<= x y) => #t

であり、かつxとyがinteger->charの定義 域内にあれば、

(<= (char->integer a)
    (char->integer b))              =>  #t

(char<=? (integer->char x)
         (integer->char y))         =>  #t

である。

[[ライブラリ手続き]] (char-upcase char)
[[ライブラリ手続き]] (char-downcase char)

以上の手続きは、(char-ci=? char char2)である文字char2を返す。さらにcharがアルファベット文字である場合は、char-upcaseの結果は大文字、char-downcaseの結果は小文字である。

7.3.5 文字列

文字列は、文字が連続したものである。文字列は、文字の連続を二重引用符(")で括って書く。文字列内部の二重引用符は、エスケープ文字バックスラッシュ(\)を使用した場合にのみ書くことができる。例えば次のようになる。

  "The word \"recursion\" has many meanings."

文字列内部のバックスラッシュは、バックスラッシュをもう一つ使用してエスケープした場合にのみ書くことができる。文字列内部で二重引用符やバックスラッシュが後続しない単独のバックスラッシュの効果については、Schemeは指定していない。

文字列定数は二行に跨って連続することができるが、そのような文字列の正確な内容は不定である。

文字列の長さは、その中に入っている文字の数である。この数は非負の整数で、文字列が作成された時に確定する。文字列の有効な指標は、文字列の長さ未満の非負の完全整数である。文字列の最初の文字の指標は0で始まり、二番目の文字の指標は1、以下同様である。

"指標startで始まり指標endで終る<文字列>"のような語句による表現をした場合は、指標startは指標に含まれ、指標endは指標に含まれない。したがってstartとendが同一の指標である場合は長さゼロの部分文字列が参照され、startがゼロでendが文字列の長さである場合は文字列全体が参照される。

文字列に作用する手続きの中には、大文字と小文字の違いを無視するものがある。大文字と小文字を無視する手続きの名前には"-ci"("case insensitive")が入る。

[[手続き]] (string? obj)

objが文字列の場合に#tを返し、それ以外の場合は#fを返す。

[[手続き]] (make-string k)
[[手続き]] (make-string k  char)

make-stringは、長さkの文字列を新しく割り当てて返す。charが与えられた時は、文字列の要素がすべてそのcharで初期化される。それ以外の場合は文字列の内容は不定である。

[[ライブラリ手続き]] (string char ...)

引数で構成される文字列を新しく割り当てて返す。

[[手続き]] (string-length string)

渡された文字列stringの長さを返す。

[[手続き]] (string-ref string k)

kは、有効な<文字列>指標でなければならない。string-refは、開始値をゼロとする指標を使用して指標kの文字を返す。

[[手続き]] (string-set! string k  char)

kは有効な文字列指標でなければならない。string-set!は文字列stringの指標kの要素に文字charを格納して、不定の値を返す。

  (define (f) (make-string 3 #\*))
  (define (g) "***")
  (string-set! (f) 0 #\?)  =>  unspecified
  (string-set! (g) 0 #\?)  =>  error
  (string-set! (symbol->string 'immutable)
               0
               #\?)  =>  error
[[ライブラリ手続き]] (string=? string1 string2)
[[ライブラリ手続き]] (string-ci=? string1 string2)

二つの文字列が同一の長さで、かつ同一の文字が同一の場所に入っている場合に#tを返す。それ以外の場合は#fを返す。string-ci=?は大文字と小文字を同一文字であるかのように処理するが、string=?では大文字と小文字が異なる文字として処理される。

[[ライブラリ手続き]] (string<? string1 string2)
[[ライブラリ手続き]] (string>? string1 string2)
[[ライブラリ手続き]] (string<=? string1 string2)
[[ライブラリ手続き]] (string>=? string1 string2)
[[ライブラリ手続き]] (string-ci<? string1 string2)
[[ライブラリ手続き]] (string-ci>? string1 string2)
[[ライブラリ手続き]] (string-ci<=? string1 string2)
[[ライブラリ手続き]] (string-ci>=? string1 string2)

以上の手続きは、文字の順序付手続きに対応して、手続きを辞書的な意味で文字列に拡張したものである。例えばstring<?では、文字に関する順序付け手続きchar<?から帰納して、文字列を辞書的に順序付けている。二つの文字列の長さが異なっており、短い方の文字列の長さまで文字列が同一である場合は、短い方の文字列は長い方の文字列よりも辞書的に小さいと見なされる。

対応する数値述語関数と同じように、処理系は上記の手続きとstring=?およびstring-ci=?手続きを、三つ以上の引数をとるように拡張してもよい。

[[ライブラリ手続き]] (substring string start end)

stringは文字列でなければならず、startとendは

  0 <= start <= end <= (string-length string).

を満足しなければならない。substringは、指標startを含むstartから始まり指標endを除くendで終る文字で構成される文字列を、新しく割り当てて返す。

[[ライブラリ手続き]] (string-append string ...)

渡された文字列引数を連結した文字で構成される文字列を、新しく割り当てて返す。

[[ライブラリ手続き]] (string->list string)
[[ライブラリ手続き]] (list->string list)

string->listは、渡された文字列を構成する文字のリストを新しく割り当てて返す。list->stringは、文字のリストlist(31)の文字で構成される文字列を、新しく割り当てて返す。string->listlist->stringは、equal?の意味で逆関数である。

[[ライブラリ手続き]] (string-copy string)

渡された文字列stringのコピーを新しく割り当てて返す。

[[ライブラリ手続き]] (string-fill! string char)

渡された文字列stringのすべての要素にcharを格納する。返す値は不定である。

7.3.6 ベクタ

ベクタは、整数で指標付を行なった要素で構成される、異成分からなる構造である。ベクタは通常同じ長さのリストよりも占有するメモリが少く、任意に選択した要素のアクセスに必要な平均時間も、通常はリストよりも少い。

ベクタの長さは、ベクタに含まれる要素の数である。この数は非負の整数で、ベクタが作成された時に確定する。ベクタの有効な指標は、ベクタの長さよりも短い非負の完全整数である。ベクタの最初の要素には指標ゼロがつき、最後の要素にはベクタの長さよりも1少い指標がつく。

ベクタは、記法\#(obj ...)を使用して書かれる。例えば指標0の要素に数値ゼロ、指標1の要素にリスト(2 2 2 2)、指標2の要素に文字列"Anna"を含む長さ3のベクタは、次のように書く。

  #(0 (2 2 2 2) "Anna")

これがベクタの外部表現であって、ベクタに評価される式ではないことに注意する必要がある。リスト定数同様、ベクタ定数は次のようにクォートしなければならない(引用符をつける、もしくはquoteをつける)。

  '#(0 (2 2 2 2) "Anna")  =>  #(0 (2 2 2 2) "Anna")
[[手続き]] (vector? obj)

objがベクタの場合に#tを返し、それ以外の場合は#fを返す。

[[手続き]] (make-vector k)
[[手続き]] (make-vector k fill)

k個の要素を持つベクタを新しく割り当てて返す。二番目の引数が渡された場合は、要素の一つ一つがfillで初期化される。それ以外の場合は要素一つ一つの内容は不定である。

[[ライブラリ手続き]] (vector obj ...)

渡された引数を要素の内容とするベクタを、新しく割り当てて返す。listに相似の手続きである。

  (vector 'a 'b 'c)               =>  #(a b c)
[[手続き]] (vector-length vector)

ベクタvector内の要素数を完全整数で返す。

[[手続き]] (vector-ref vector k)

kはベクタvectorの有効な指標でなければならない。vector-refは、ベクタvectorの指標kの要素の内容を返す。

  (vector-ref '#(1 1 2 3 5 8 13 21)
              5)         =>  8
  (vector-ref '#(1 1 2 3 5 8 13 21)
              (let ((i (round (* 2 (acos -1)))))
                (if (inexact? i)
                    (inexact->exact i)
                    i))) => 13
[[手続き]] (vector-set! vector k obj)

kはベクタvectorの有効な指標でなければならない。vector-set!は、オブジェクトobjをベクタvectorの指標kの要素に格納する。vector-set!が返す値は不定である。

  (let ((vec (vector 0 '(2 2 2 2) "Anna")))
    (vector-set! vec 1 '("Sue" "Sue"))
    vec)      =>  #(0 ("Sue" "Sue") "Anna")

  (vector-set! '#(0 1 2) 1 "doe")  =>  error  ; constant vector
[[ライブラリ手続き]] (vector->list vector)
[[ライブラリ手続き]] (list->vector list)

vector->listは、ベクタvectorの要素に含まれるオブジェクトをリストにして、新しく割り当てて返す。list->vectorはベクタを新に作成し、その要素をリストlistの要素に初期化して返す。

  (vector->list '#(dah dah didah))  =>  (dah dah didah)
  (list->vector '(dididit dah))     =>  #(dididit dah)
[[ライブラリ手続き]] (vector-fill! vector fill)

ベクタvectorの一つ一つの要素すべてにfillを格納する。 vector-fill!が返す値は不定である。

7.4 制御機能

本章では、プログラムを実行する流れを特別な方法で制御する、各種のプリミティブ手続きを説明する。述語関数procedure?についても本章で説明する。

[[手続き]] (procedure? obj)

objが手続きであれば#tを返し、それ以外の場合は#fを返す。

  (procedure? car)            =>  #t
  (procedure? 'car)           =>  #f
  (procedure? (lambda (x) (* x x)))
                              =>  #t
  (procedure? '(lambda (x) (* x x)))
                              =>  #f
  (call-with-current-continuation procedure?)
                              =>  #t
[[手続き]] (apply proc arg1 ... args)

procは手続き、argsはリストでなければならない。リスト(append (list arg1 ...) args)の要素を実引数に使用してprocを呼び出す。

  (apply + (list 3 4))              =>  7

  (define compose
    (lambda (f g)
      (lambda args
        (f (apply g args)))))

  ((compose sqrt *) 12 75)          =>  30
[[ライブラリ手続き]] (map proc list1 list2 ...)

一連のlistはリストでなければならず、procは存在する限りのlistを引数にとって単一の値を返す手続きでなければならない。二つ以上のlistを渡す場合、リストはすべて同じ長さでなければならない。mapはlistの要素一つ一つにprocを適用して、もとの順序を変えずに結果をリストにして返す。listの要素にprocが適用される動的な順序は不定である。

  (map cadr '((a b) (d e) (g h)))   =>  (b e h)

  (map (lambda (n) (expt n n))
       '(1 2 3 4 5))                =>  (1 4 27 256 3125)

  (map + '(1 2 3) '(4 5 6))         =>  (5 7 9)

  (let ((count 0))
    (map (lambda (ignored)
           (set! count (+ count 1))
           count)
         '(a b)))                   =>  (1 2) or (2 1)
[[ライブラリ手続き]] (for-each proc list1 list2 ...)

for-eachの引数はmapのものに似ているが、値ではなく副作用を目的にして手続きを呼び出す点が違っている。mapとは異なりfor-eachは、最初から最後に向かう順序でリストの要素に対して手続きを呼び出すことが保証されている。for-eachが返す値は不定である。

  (let ((v (make-vector 5)))
    (for-each (lambda (i)
                (vector-set! v i (* i i)))
              '(0 1 2 3 4))
    v)                                =>  #(0 1 4 9 16)
[[ライブラリ手続き]] (force promise)

プロミスpromiseの値を強制的に引き出す(delay、section 5.2.5 遅延評価参照)。プロミスの値が以前に計算されていない場合は、この時に値を計算して返す。プロミスの値はキャッシュされる(memoizeされる)(32)ので、二度目にforceで呼び出された時には以前に計算した値が返される。

  (force (delay (+ 1 2)))        => 3
  (let ((p (delay (+ 1 2))))
    (list (force p) (force p)))  => (3 3)

  (define a--stream
    (letrec ((next
              (lambda (n)
                (cons n (delay (next (+ n 1)))))))
      (next 0))) 
  (33)

  (define head car) 
  (define tail
    (lambda (stream) (force (cdr stream))))

  (head (tail (tail a--stream))) => 2

forcedelayは、関数型プログラムでプログラミングを行なう場合を主として想定している。以下の例は、いいプログラミングスタイルを示すためのものではない。これはforce手続きが何度呼び出された場合でも、一つのプロミスについてただ一つの値だけが計算されるという特徴を説明するものである。

  (define count 0) 
  (define p
    (delay (begin (set! count (+ count 1))
                  (if (> count x)
                      count 
                      (force p)))))
  (34)

  (define x 5) 
  p                 => *プロミス*
  (force p)         => 6 
  p                 => *やはりプロミス*
  (begin (set! x 10)
         (force p)) => 6

delayforceの可能な実装例を次に示す。この例ではプロミスは引数を持たない手続きとして実装されており、forceは単にforce自体の引数を(手続きとして--訳者注)呼び出しているだけである。

  (define force
    (lambda (object)
      (object)))(35)

次の式

  (delay <式>) 

は、次の手続き呼び出しと同じ意味を持つように定義される。

  (make-promise (lambda () <式>))

例えば次の通りである。

  (define-syntax delay
    (syntax-rules ()
      ((delay expression)
       (make-promise (lambda () expression))))),%

ただしmake-promiseは次のように定義される。

  (define make-promise
    (lambda (proc)
      (let ((result-ready? #f)
            (result #f))
        (lambda ()
          (if result-ready?
              result
              (let ((x (proc)))
                (if result-ready?
                    result
                    (begin (set! result-ready? #t)
                           (set! result x)
                           result))))))))

理論的根拠:

上記の最後の例に示すように、プロミスはプロミス自体の固有の 値を参照することができる。この様なプロミスをforceで呼び出 した場合、最初のforceが計算される前にプロミスに対する二度 目のforceが発生することがあり、そのためにmake-promise の定義が複雑になる。

処理系によっては次のように、このdelayforceの構文の拡張をサポートするものがある。

[[手続き]] (call-with-current-continuation proc)

procは、引数を一つとる手続きでなければならない。call-with-current-continuationは現在のコンティニュエーション(下記の理論的根拠参照)を"エスケープ手続き"として一つの環境にまとめ、それを引数としてprocに渡す。Scheme手続きであるこのエスケープ手続きが後で呼び出されると、その時に有効なあらゆるコンティニュエーションが放棄され、かわりにエスケープ手続きが作成された時に有効であったコンティニュエーションが使用されるようになる。このエスケープ手続きを呼び出すと、dynamic-windを使用してインストールされたbeforeとafterの手続きが呼び出される場合がある。

このエスケープ手続きはコンティニュエーションとして、もともとのcall-with-current-continuation呼び出しと同数の引数を受け付ける。call-with-values手続きで作成されたコンティニュエーションを除いて、すべてのコンティニュエーションは正確に1つの値をとる。call-with-valuesが作成した以外のコンティニュエーションに値を渡さなかった場合、もしくは2つ以上の値を渡した場合、その結果は不定である。

procに渡されるエスケープ手続きは、Schemeのその他のあらゆる手続きと同じく無限エクステントを持つ。この手続きは変数やデータ構造に保管して、必要な回数だけ呼び出すことができる。

次の例はcall-with-current-continuationの最も一般的な利用法を示すためだけにあげるものである。現実のプログラムがこの例のように単純であるならば、call-with-current-continuationのように強力な手続きは不要であろう。

  (call-with-current-continuation
    (lambda (exit)
      (for-each (lambda (x)
                  (if (negative? x)
                      (exit x)))
                '(54 0 37 -3 245 19))
      #t))                        =>  -3

  (define list-length
    (lambda (obj)
      (call-with-current-continuation
        (lambda (return)
          (letrec ((r
                    (lambda (obj)
                      (cond ((null? obj) 0)
                            ((pair? obj)
                             (+ (r (cdr obj)) 1))
                            (else (return #f))))))
            (r obj))))))
(36)

  (list-length '(1 2 3 4))            =>  4

  (list-length '(a b . c))            =>  #f

理論的根拠:

call-with-current-continuationの一般的な利用法は ループや手続き本体からの構造化された大域脱出であるが、 広範囲にわたる先進的な制御構造を実装する場合には、 call-with-current-continuationは極めて有用である。

Schemeの式が評価される時は、式の結果を待っているコンティ ニュエーションが必ず存在する。このコンティニュエーションは、 計算に対応する未来の全体(デフォルト)を表すものである。 例えば式がトップレベルで評価される場合、結果を受け取り、 画面に表示し、次の入力を求めてそれを評価しという具合に、 これを無限に繰り返すのがコンティニュエーションと言える。 結果を受け取って局所変数に保管された値を乗じ、7を加えて 答をトップレベルコンティニュエーションに渡して画面に表示 するといった局所コンティニュエーションの場合のように、 ほとんどの場合このコンティニュエーションにはユーザのコー ドで指定される動作が含まれる。このように至るところに 存在するコンティニュエーションは通常その時々の状況の影に 隠れており、プログラマがそれについて深く考えることはない。 ただし稀には、各種のコンティニュエーションを明示的に操作 する必要がプログラマに生ずることがある。 call-with-current-continuationによって現在のコン ティニュエーションであるかのように動作する手続きを作成 すれば、Schemeプログラマはコンティニュエーションを明示的に 操作できる。

ほとんどのプログラミング言語には、exitreturn、あるいはさらにgotoの様な名前の、 特別な目的のエスケープ構造が一つ以上組み込まれている。 しかしながら1965年にはPeter Landin[LANDIN65]が J--operatorと呼ばれる汎用目的のエスケープ操作を発明した。 1972年にはJohn Reynolds[REYNOLDS72]が、より 単純ながら同じように強力な構造を発表した。1975年の Schemeに関する報告書でSussmanとSteeleが発表した catch特殊形式は、まさにReynoldsの構造と同じもの であったが、その名前はさほど一般的でないMaclispの構造 から取られたものであった。catch構造の強力さの 全貌は、特殊構文の構造ではなく手続きによって得られること に気がついたScheme処理系の複数の実装者が、1982年に call-with-current-continuationの名前を考案 した。この名前は手続きをよく表しているが、この様な 長い名前を使うメリットについては異論もあって、代わりに call/ccという名前を使用する人々も存在する。

[[手続き]] (values obj ...)

渡されたすべての引数を自分のコンティニュエーションに渡す。call-with-values手続きで作成されたコンティニュエーションの場合を除いて、すべてのコンティニュエーションは正確に1つの値をとる。valuesは次のように定義することができる。

  (define (values . things)
    (call-with-current-continuation 
      (lambda (cont) (apply cont things))))
[[手続き]] (call-with-values producer consumer)

値を渡さずにproducer引数を呼び出し、ついで何らかの値を渡されたときはその値を引数としてconsumer手続きを呼び出すコンティニュエーションを呼び出す。consumerを呼び出す際のコンティニュエーションは、call-with-valuesを呼び出すコンティニュエーションである。

  (call-with-values (lambda () (values 4 5))
                    (lambda (a b) b))                =>  5
(37)

  (call-with-values * -)                             =>  -1
(38)
[[手続き]] (dynamic-wind before thunk after)

引数なしでthunkを呼び出し、その呼び出しの結果を返す。下記の規則の指定にしたがって、beforeとafterがやはり引数なしで呼び出される(call-with-current-continuationを使用して捕獲したコンティニュエーションへの呼び出しがない場合、3つの引数が順序通りに各々一回だけ呼び出される点に注意する必要がある)。実行がthunkの呼び出しの動的エクステントに入ったときはbeforeが必ず呼び出され、その動的エクステントが終了したときは必ずafterが呼び出される。手続き呼び出しの動的エクステントは、呼び出しが開始した時点と呼び出しが返った時点との間の期間である。Schemeではcall-with-current-continuationがあるために、呼び出しの動的エクステントが単一の連続した期間でない場合がある。これは次のように定義される。

thunkの呼び出しの動的エクステント内部で2番目のdynamic-windの呼び出しが発生し、2度のdynamic-windの起動による2つのafterを両方とも呼び出す必要が生ずるようなコンティニュエーションが呼び出された場合は、2番目の(内側の)dynamic-wind呼び出しに連結されたafterが最初に呼び出される。

thunkの呼び出しの動的エクステント内部で2番目のdynamic-windの呼び出しが発生し、2度のdynamic-windの起動による2つのbeforeを両方とも呼び出す必要が生ずるようなコンティニュエーションが呼び出された場合は、最初の(外側の)dynamic-wind呼び出しに連結されたbeforeが最初に呼び出される。

1つのdynamic-wind呼び出しのbeforeと別のdynamic-wind呼び出しのafterがコンティニュエーションの起動に必要な場合は、afterが最初に呼び出される。

捕獲したコンティニュエーションを使用してbeforeやafterの動的エクステントを開始、もしくは終了した場合の結果は未定義である。

  (let ((path '())
        (c #f))
    (let ((add (lambda (s)
                 (set! path (cons s path)))))
      (dynamic-wind
        (lambda () (add 'connect))
        (lambda ()
          (add (call-with-current-continuation
                 (lambda (c0)
                   (set! c c0)
                   'talk1))))
        (lambda () (add 'disconnect)))
      (if (< (length path) 4)
          (c 'talk2)
          (reverse path))))
      => (connect talk1 disconnect
                   connect talk2 disconnect)

7.5 Eval

[[手続き]] (eval expression environment-specifier)

指定環境でexpressionを評価し、expressionの値を返す。expressionはデータとして表現された有効なSchemeの式でなければならず、environment-specifierは下記に示す3つの手続きの1つが返す値でなければならない。処理系はevalを拡張して式以外のプログラム(定義)を第1引数に許可し、その他の値を環境として許可してもよい。ただしnull-environmentscheme-report-environmentに連結された環境に、evalによる新しいバインディングの作成が許可されないような制限が必要である。

  (eval '(* 7 3) (scheme-report-environment 5))      =>  21

  (let ((f (eval '(lambda (f x) (f x x))
                 (null-environment 5))))
    (f + 10))                                        =>  20
[[手続き]] (scheme-report-environment version)
[[手続き]] (null-environment version)

versionは、Scheme報告書(the Revised^5 Report on Scheme)の今回の改定版に対応する完全整数5でなければならない。scheme-report-environmentは必須、もしくはオプションであっても処理系がサポートするとして本報告書に定義するすべてのバインディング以外は空の、環境を指定する手続きを返す。null-environmentは必須、もしくはオプションであっても処理系がサポートするとして本報告書に定義するすべての構文キーワードの(構文)バインディング以外は空の、環境を指定する手続きを返す。

その他のversionの値を使用して、本報告書の過去の改定に一致する環境を指定することもできるが、この機能のサポートは必須ではない。versionが5でもなく処理系がサポートするもう1つの値でもない場合、処理系はエラーを発信する。

scheme-report-environmentでバインドされている変数に(evalを使用して)割り当てを行なった場合の結果は不定である。したがって scheme-report-environmentで指定された環境は、変更できない場合がある。

[[オプション手続き]] (interaction-environment)

この手続きは処理系定義のバインディング、標準的には本報告書にあげたバインディングの上位集合を含む環境を指定する手続きを返す。目的はこの手続きによって、ユーザが動的に型を定義した式を処理系が評価する環境を返すことである。

7.6 入出力

7.6.1 ポート

ポートは入出力装置を表す。Schemeにとっての入力ポートはコマンドに応じて文字を送出できるSchemeオブジェクトである。対する出力ポートは、文字を受け付けるSchemeオブジェクトである。

[[ライブラリ手続き]] (call-with-input-file string proc)
[[ライブラリ手続き]] (call-with-output-file string proc)

stringはファイルの名前を指定する文字列、procは引数を一つ受け入れる手続きである。call-with-input-fileの場合、ファイルはすでに存在しているものでなければならない。call-with-output-fileについてファイルがすでに存在している場合、呼び出しの結果は不定である。この二つの手続きは入力もしくは出力用に名前を指定したファイルを開いて、得られたボートを引数に一つ取る手続きprocを呼び出す。ファイルを開けなかった場合はエラー信号が発せられる。手続きが戻る場合はポートが自動的に閉じられて、手続きが生成した値が返される。手続きが戻らない場合、readもしくはwrite操作でポートが二度と使用されないことが証明できない限り、ポートが自動的に閉じられることはない。

理論的根拠:

Schemeのエスケープ手続きは無限エクステントを持つことから、 現在のコンティニュエーションからエスケープで脱出して後で エスケープで戻ることができる。現在のコンティニュエーション からエスケープ脱出した時にポートを閉じることを処理系に 許した場合、call-with-current-continuationcall-with-input-file、もしくは call-with-current-continuationcall-with-output-fileを使用して移植可能な コードを書くことができなくなるであろう。

[[手続き]] (input-port? obj)
[[手続き]] (output-port? obj)

objが入力ポートか出力ポートであれば#tを返し、それ以外の場合は#fを返す。

[[手続き]] (current-input-port)
[[手続き]] (current-output-port)

現在のデフォルトの入力ポートか出力ポートを返す。

[[オプション手続き]] (with-input-from-file string thunk)
[[オプション手続き]] (with-output-to-file string thunk)

stringはファイルを指す文字列、proc(39)は引数を取らない手続きでなければならない。with-input-from-fileの場合、ファイルはすでに存在しているものでなければならない。with-output-to-fileについては、ファイルがすでに存在している場合の結果は不定である。ファイルは入力もしくは出力用に開かれ、ファイルに連結された入力ポートもしくは出力ポートが、current-input-portもしくはcurrent-output-portのデフォルトの値になる(このポートを(read)(write obj)などが使用する)。その上で引数をとらない手続きthunkが呼び出される。thunkが返るとポートが閉じられ、以前のデフォルト値が回復する。with-input-from-filewith-output-to-fileは、thunkが生成した値を返す。このコンティニュエーションからの脱出にエスケープ手続きが使用された場合、二つの手続きの振舞いは処理系に依存する。

[[手続き]] (open-input-file filename)

既存のファイルを指定する文字列を引数にとって、そのファイルの文字を送出できる入力ポートを返す。ファイルを開くことができなかった場合はエラー信号が発せられる。

[[手続き]] (open-output-file filename)

作成する出力ファイルを指定する文字列を引数にとって、その名前で新しいファイルに文字を書き出すことができる出力ポートを返す。渡された名前のファイルがすでに存在する場合、結果は不定である。

[[手続き]] (close-input-port port)
[[手続き]] (close-output-port port)

ポートportに連結したファイルを閉じて、文字を送出したり受け入れたりしていたportを使用禁止にする。ファイルがすでに閉じられていた場合はこの二つのルーチンは何も行なわない。返される値は不定である。

7.6.2 入力

[[ライブラリ手続き]] (read)
[[ライブラリ手続き]] (read port)

readはSchemeオブジェクトの外部表現をオブジェクト自体に変換する。すなわちreadは、非終端<データ>(section 8.1 構文則とsection 7.3.2 ペアとリスト参照)のパーサ(構文解析手続き)である。readはオブジェクトの外部表現の終端の次の、最初の文字を指すようにポートportを更新して、与えられた入力ポートから構文解析可能な次のオブジェクトを返す。

オブジェクトの開始となり得る文字を発見する前に入力中にファイル終りが検出された場合は、ファイル終りオブジェクトが返される。portは開いたままであり、さらに読み取りを試みた場合にもファイル終りオブジェクトが返される。オブジェクトの外部表現が開始した後にその外部表現が不完全であるために構文解析できず、ファイル終りが検出された場合は、エラーが発信される。

port引数は省略できる。この場合のデフォルトはcurrent-input-portが返す値である。閉じたポートから読み取りを行なった場合はエラーである。

[[手続き]] (read-char)
[[手続き]] (read-char port)

入力ポートportから取り出すことができる次の文字を返し、その次の文字を指すようにポートを更新する(ポートを一つ進める)。取り出す文字が存在しなかった場合はファイル終りオブジェクトが返される。port引数は省略でき、その場合のデフォルトはcurrent-input-portが返す値である。

[[手続き]] (peek-char)
[[手続き]] (peek-char port)

入力ポートportから取り出すことができる次の文字を返すが、次の文字を指し示す様なポートの更新は行なわれない(ポート内の位置は変わらない)。取り出す文字が存在しなかった場合はファイル終りオブジェクトが返される。port引数は省略でき、その場合のデフォルトはcurrent-input-portが返す値である。

注: peek-charの呼び出しで返される値は、同じポートに対するread-charの呼び出しで返される値と同じものである。唯一の違いは、ポートに対する直後のread-char呼び出しもしくはpeek-char呼び出しで、直前のpeek-char呼び出しで返された値が返されるという点である。特に対話型ポートに対してpeek-charを呼び出した場合、入力を待ち続けてread-charがハングする時には、peek-charも必ずハングすることになる。

[[手続き]] (eof-object? obj)

objがファイル終りオブジェクトの場合に#tを返し、それ以外の場合は#fを返す。ファイル終りオブジェクトの正確な集合は処理系ごとに異なるが、いかなる場合にも、ファイル終りオブジェクトがreadを使用して読み取ることができるオブジェクトになることはない。

[[手続き]] (char-ready?)
[[手続き]] (char-ready? port)

入力ポートportに文字の準備ができている場合に#tを返し、それ以外の場合は#fを返す。char-ready?が#tを返した場合、与えられたポートに対する次のread-char操作はハングしないことが保証される。ポートがファイル終りにある場合、char-ready?は#tを返す。port引数は省略でき、その場合のデフォルトはcurrent-input-portが返す値である。

理論的根拠:

char-ready?は、入力を待ち続けてスタックすること なく、対話型ポートからプログラムが文字を受け入れることが できるようにするために存在する。この様なポートに接続 されたあらゆる入力エディターは、char-ready?が 存在を肯定した文字を削除できないことを保証しなければ ならない。ファイル終りでchar-ready?が#fを返す 様なことがあるならば、ファイル終りにあるポートを文字 の準備ができていない対話型ポートから区別することがで きなくなるであろう。

7.6.3 出力

[[ライブラリ手続き]] (write obj)
[[ライブラリ手続き]] (write obj port)

objの文字による表現を与えられたポートportに書き出す。文字表現内に現れる文字列は二重引用符で括られ、その文字列内のバックスラッシュ文字と二重引用符文字にはエスケープ文字バックスラッシュが付けられる。writeが返す値は不定である。port引数は省略でき、その場合のデフォルトはcurrent-output-portが返す値である。

[[ライブラリ手続き]] (display obj)
[[ライブラリ手続き]] (display obj port)

objの文字による表現を与えられたポートportに書き出す。文字表現内に現れる文字列は二重引用符で括られず、その文字列内のいかなる文字にもエスケープ文字は付かない。この表現内の文字オブジェクトは、writeではなくwrite-charで書かれたかのように表示される。displayが返す値は不定である。port引数は省略でき、その場合のデフォルトはcurrent-output-portが返す値である。

理論的根拠:

writeは機械が読みやすい出力を生成し、displayは 人間が読みやすい出力を生成することを目的としている。シンボル 内に"スラッシュ化"(slashification)を許す処理系では、 シンボルの特殊文字を"スラッシュ化"するにはdisplay ではなくwriteの方がおそらく好ましいであろう。

[[ライブラリ手続き]] (newline)
[[ライブラリ手続き]] (newline port)

行の終りをポートportに書き出す。これが正確にどのように行なわれるかは、オペレーティングシステムごとに異なる。不定の値が返される。port引数は省略でき、その場合のデフォルトはcurrent-input-portが返す値である。

[[手続き]] (write-char char)
[[手続き]] (write-char char port)

文字char(文字の外部表現ではない)を与えられたポートportに書き出し、不定の値を返す。port引数は省略でき、その場合のデフォルトはcurrent-output-portが返す値である。

7.6.4 システムインタフェース

一般的には、システムインタフェースの問題は本報告書の領域外に属す。ただし以下の操作は非常に重要であり、本節で説明するに値する。

[[オプション手続き]] (load filename)

filenameは、Schemeのソースコードが入っている既存のファイルを指定する文字列でなければならない。load手続きはファイルから式と定義を読み込んで、それを順次的に評価する。式の結果が表示されるかどうかは指定されていない。current-input-portcurrent-output-portが返す値は、load手続きによっては変更されない。loadは不定の値を返す。

理論的根拠:

移植性を確保するためには、loadはソースファイルに 対して動作しなければならない。その他のファイルに対する 動作が処理系ごとに異なることはやむを得ない。

[[オプション手続き]] (transcript-on filename)
[[オプション手続き]] (transcript-off)

filenameは、作成する出力ファイルを指定する文字列でなければならない。transcript-onの結果指定されたファイルが出力用に開かれて、以後のユーザとSchemeシステムとの対話の記録がそのファイルに書き出されるようになる。記録の出力はtranscript-offの呼び出しで終了し、それによって記録ファイルが閉じられる。いつの時点でも、開いている記録ファイルはただ一つだけである。ただし処理系によってはこの制限を緩める場合がある。この二つの手続きが返す値は不定である。


[Contents]   [Back]   [Prev]   [Up]   [Next]   [Forward]