データ型は関連する値の集まりです。この集まりは、互いに素である 必要はなく、多くの場合には階層構造になります。Schemeには豊富なデータ型 があり、いくつかのものは、単純(分割不可能)なもので、それ以外は他のデータ型 を組み合せて合成したデータ型です。
Schemeの単純データ型には、真理値、数値、文字、シンボルがあります。
Schemeの真理値は、真は#t
、偽は#f
です。
Schemeには引数が真理値であるかどうかを判定するboolean?
という
述語手続きがあります。
(boolean? #t) => #t (boolean? "Hello, World!") => #f
手続きnot
は引数を真理値だと考えてこれを否定します。
(not #f) => #t (not #t) => #f (not "Hello, World!") => #f
最後の式はSchemeの便利なところで、真理値が必要な文脈では、
Schemeは#f
ではない値はすべて真の値として扱います。
Schemeの数値は、整数(たとえば、42
)、有理数(22/7
)、
実数(3.1416
)、複素数(2+3i
)になりえます。
整数は有理数の一部であり、有理数は実数の一部であり、
実数は複素数の一部です。いろいろな数の種類について
テストする述語が存在します。
(number? 42) => #t (number? #t) => #f (complex? 2+3i) => #t (real? 2+3i) => #f (real? 3.1416) => #t (real? 22/7) => #t (real? 42) => #t (rational? 2+3i) => #f (rational? 3.1416) => #t (rational? 22/7) => #t (integer? 22/7) => #f (integer? 42) => #t
Schemeの整数は10進表示でなければならないわけではありません。
#b
を接頭辞としてもちいれば、2進数表示であることを指定できます。
したがって、#b1100
は十二のことです。8進表示の接頭辞は#o
、
16進表示の接頭辞は#x
です。(10進表示であることを明示的にしめすには
接頭辞#d
をつかいます。)
数値の同値性のチェックには汎用同値性述語eqv?
を使うことができます。
(eqv? 42 42) => #t (eqv? 42 #f) => #f (eqv? 42 42.0) => #f
しかし、もし比較すべき引数が数値だとわかっているなら、数値専用同値性
述語=
を使うのがよりふさわしいです。
(= 42 42) => #t (= 42 #f) -->ERROR!!! (= 42 42.0) => #t
そのほかにも、
<
、<=
、>
、>=
の比較をすることができます。
(< 3 2) => #f (>= 4.5 3) => #t
算術手続き、+
、-
、*
、/
、expt
の振舞いは以下の
とおりです。
(+ 1 2 3) => 6 (- 5.3 2) => 3.3 (- 5 2 1) => 2 (* 1 2 3) => 6 (/ 6 3) => 2 (/ 22 7) => 22/7 (expt 2 3) => 8 (expt 4 1/2) => 2.0
-
および/
は、引数が一つの場合には、それぞれ、符号反転、
逆数を返します。
(- 4) => -4 (/ 4) => 1/4
手続きmax
およびmin
はそれぞれ、引数として与えられた数値の
最大、最小を返します。引数の数はいくつでもかまいません。
(max 1 3 4 2 3) => 4 (min 1 3 4 2 3) => 1
手続きabs
は引数であたえられた数値の絶対値を返します。
(abs 3) => 3 (abs -4) => 4
これは氷山の一角にすぎません。Schemeは
算術演算や三角関数の大規模で包括的な一式を提供します。
たとえば、atan
、exp
およびsqrt
は、それぞれ、
その引数の逆正接、自然逆対数および平方根を返します。
詳細はR5RS[cite{r5rs}]を参照してください。
Schemeの文字データは接頭辞#\
を使って表わします。したがって、
#\c
は文字c
のことです。いくつかの非表示文字にはより
説明的な名前がついています。たとえば、#\newline
、#\tab
などです。
空白文字は#\
、あるいはより読みやすく#\space
と
書くことができます。
文字述語はchar?
です。
(char? #\c) => #t (char? 1) => #f (char? #\;) => #t
セミコロン文字はコメントの契機にはならないことに注意してください。
文字データ型には次の比較述語一式があります。
char=?
、char<?
、char<=?
、char>?
、char>=?
です。
(char=? #\a #\a) => #t (char<? #\a #\b) => #t (char>=? #\a #\b) => #f
大文字、小文字の違いを無視した比較を行うには、手続き名に
char
ではなくchar-ci
のついた述語を使います。
(char-ci=? #\a #\A) => #t (char-ci<? #\a #\B) => #t
大文字、小文字の変換を行う手続きは、char-downcase
および
(char-downcase #\A) => #\a (char-upcase #\a) => #\A
上でみた単純データ型はどれも自己評価的です。すなわち、 これらのデータ型のうちどのオブジェクトをリスナーに入力しても リスナーによって返される評価結果は、入力したものと同じになるでしょう。
#t => #t 42 => 42 #\c => #\c
シンボルの振舞いは同じではありません。シンボルは Schemeのプログラムでは変数の識別子として使われる ので、評価されて、その変数がもつ値になるからです。それでもなお、 シンボルは単純データ型です。シンボルは正当な値で、文字や数値 その他の値と変換可能なものです。
Schemeによって変数と解釈されないシンボルを指定するには シンボルをクウォートしなければなりません。
(quote xyz) => xyz
このタイプのクウォートはSchemeでは良くつかうので、簡略記法が 用意されています。式
'E
はSchemeでは以下と同じです。
(quote E)
Schemeのシンボルは文字列により名前がついています。
シンボル名の唯一の制限は他のデータ(つまり、文字、真理値)と取り違えるよう
なものではいけないということです。それゆえ、this-is-a-symbol
、
i18n
、<=>
、$!#*
はすべてシンボルで、16
、-i
(複素数!)
#t
、"this-is-a-string"
、(barf)
(リスト)はシンボルではありません。
シンボルかどうかをチェックするには述語symbol?
を使います。
(symbol? 'xyz) => #t (symbol? 42) => #f
Scheme のシンボルは通常、大文字と小文字を区別しません。
したがって、Calorie
とcalorie
は等しいシンボルです。
(eqv? 'Calorie 'calorie) => #t
シンボルxyz
をフォームdefine
をつかってグローバル変数として
利用することができます。
(define xyz 9)
これは、変数xyz
が値9
を保持するといっています。xyz
を
リスナーに食わせると結果はxyz
の保持するその値になります。
xyz => 9
変数が保持している値を変更するには、フォームset!
が使えます。
(set! xyz #\c)
とすると
xyz => #\c
合成データ型は構造化された方法で他のデータ型から値を合成することによって 構築されます。
文字列は文字の並び(名前として文字のならびをもつ単純データ、シンボル と混同してはいけません)です。一連の文字の並びを二重引用符で囲むことで 文字列を指定できます。文字列は評価すると、その文字列自身になります。
"Hello, World!" => "Hello, World!"
手続きstring
は一団の文字のをとり、その文字からなる文字列を
返します。
(string #\H #\e #\l #\l #\o) => "Hello"
ではグローバル変数greeting
を定義してみよう。
(define greeting "Hello; Hello!")
文字列データ内のセミコロンはコメントの引き金にはならないことに 注意してください。
与えられた文字列中の文字は個別にアクセスしたり変更したりすることが
できます。手続きstring-ref
は一つの文字列と(0基準の)インデックス
をとり、そのインデックスの文字を返します。
(string-ref greeting 0) => #\H
新しい文字列は別の文字列を連結することで作ることができます。
(string-append "E " "Pluribus " "Unum") => "E Pluribus Unum"
長さを指定して文字列を作ることができます。あとから、好きな文字を 埋めることができます。
(define a-3-char-long-string (make-string 3))
文字列かどうかをチェックする述語はstring?
です。
string
、make-string
、string-append
の結果として
得られた文字列は可変です。手続きstring-set!
は与えられた
インデックスの文字を置き換えます。
(define hello (string #\H #\e #\l #\l #\o)) hello => "Hello" (string-set! hello 1 #\a) hello => "Hallo"
ベクタは文字列のような並べですが、その要素は文字以外のなにでも かまいません。ベクタ自身でもかまわないので、これは多次元ベクタを 作るよい方法です。
Here’s a way to create a vector of the first five integers: 最初の5つの整数からなるベクタをこのように作ります。
(vector 0 1 2 3 4) => #(0 1 2 3 4)
Schemeにおけるベクタの値の表現:要素を括弧でかこみ、#
を頭につけます。
make-string
と同様に、プロシージャ手続きmake-vector
は指定した
長さのベクタを作ります。
(define v (make-vector 5))
手続きvector-ref
およびvector-set!
はベクタの要素にアクセス
あるいは変更します。
ベクタかどうかをチェックする述語はvector?
です。
ドット対は二つの任意の値を組合せて順序対にすることで
作りだされた合成値です。最初の要素はcar、二番目の要素は
cdrと呼ばれ、組合せの手続きはcons
です。
(cons 1 #t) => (1 . #t)
ドット対は自己評価的ではありません。そこで、データとして
直接(すなわちcons
呼び出しによって作りだすことなく)指定するには
明示的にクウォートしなければなりません。
'(1 . #t) => (1 . #t) (1 . #t) -->ERROR!!!
アクセサ手続きはcar
とcdr
です。
(define x (cons 1 #t)) (car x) => 1 (cdr x) => #t
ドット対の要素は変更子手続きset-car!
およびset-cdr!
によって
とりかえることができます。
(set-car! x 2) (set-cdr! x #f) x => (2 . #f)
ドット対に別のドット対を含めることができます。
(define y (cons (cons 1 2) 3)) y => ((1 . 2) . 3)
このリストのcar
のcar
は1
です。
そして、car
のcdr
は2
です。すなわち、
(car (car y)) => 1 (cdr (car y)) => 2
Schemeはcar
とcdr
手続きのカスケードした組み合わせに
対応する省略形手続きを提供しています。つまり、caar
は
car
のcar
を、cdar
はcar
のcdr
を表わします。
(caar y) => 1 (cdar y) => 2
c...r
スタイルの省略形は4段のカスケードまでは存在することが
保証されています。つまり、cadr
、cdadr
、cdaddr
はいつでも
使えます。cdadadr
は頑張ってるかもしれません。
第二要素についてドットが入れ子になっているとき、その結果の式については Schemeは特別な表記を使います。
(cons 1 (cons 2 (cons 3 (cons 4 5)))) => (1 2 3 4 . 5)
つまり、(1 2 3 4 . 5)
は(1 . (2 . (3 . (4 . 5)))
の
省略形なのです。この式の最後のcdrは5
です。
Schemeは最後のcdrが空リストと呼ばれる特別なオブジェクト
(これは()
と表現される式)の場合の省略形を提供しています。
空リストは自己評価的ではないと考えられています。なので、
プログラム中で値として使うには、クウォートすべきです。
'() => ()
ドット対フォーム(1 . (2 . (3 . (4 . ()))))
の省略形は
(1 2 3 4)
です。
この入れ子になった特別な種類のドット対をリストと呼びます。 件のリストは4要素長であるといいます。これは、
(cons 1 (cons 2 (cons 3 (cons 4 '()))))
として作ることもできますが、Schemeにはlist
と呼ばれる手続きがあり、
もっと、便利にリストを作ることができます。list
は任意個の引数をとり、
それを含むリストを返します。
(list 1 2 3 4) => (1 2 3 4)
どんな要素がリストにあるのかが全部わかっているなら、 リストを指定するのにクウォートがつかえます。
'(1 2 3 4) => (1 2 3 4)
(define y (list 1 2 3 4)) (list-ref y 0) => 1 (list-ref y 3) => 4 (list-tail y 1) => (2 3 4) (list-tail y 3) => (4)
list-tail
は与えられたインデックスから始まる後の
部分のリストを返します。
述語pair?
、list?
、null?
はそれぞれ引数が、ドット対か、
リストか、空リストかを判定します。
(pair? '(1 . 2)) => #t (pair? '(1 2)) => #t (pair? '()) => #f (list? '()) => #t (null? '()) => #t (list? '(1 2)) => #t (list? '(1 . 2)) => #f (null? '(1 2)) => #f (null? '(1 . 2)) => #f
Schemeではデータ型間の変換をおこなう手続きが多数提供されています。
すでに、char-downcase
およびchar-upcase
をつかって、
大文字小文字変換をする方法を知りました。文字はchar->integer
を用いて整数に変換することができます。また整数はinteger->char
を
用いて文字に変換することができます(通常、文字に対応する整数というのは
その文字のASCIIコードです)。
(char->integer #\d) => 100 (integer->char 50) => #\2
文字列は対応する文字のリストに変換することができます。
(string->list "hello") => (#\h #\e #\l #\l #\o)
おなじ流れで別の変換手続きがあります。
list->string
、vector->list
およびlist->vector
です。
数値は文字列に変換することができます。
(number->string 16) => "16"
文字列は数値に変換することができます。もし対応する数字がない場合には
#f
が返ります。
(string->number "16") => 16 (string->number "Am I a hot number?") => #f
string->number
の基数を示す二つ目のオプション引数をとります。
(string->number "16" 8) => 14
8進数の16
は十四です。
シンボルと文字列は互いに変換できます。
(symbol->string 'symbol) => "symbol" (string->symbol "string") => string
Schemeには、そのほかのデータ型がいくつかあります。
ひとつは手続きです。すでにいろいろな手続きを見ました。
たとえば、display
、+
、cons
です。実際、これらは
手続き値をもつ変数です。値そのものは数値や文字のように、
目に見えるものではありません。
cons => <procedure>
いままで見てきた手続きはプリミティブ手続きで、標準的な グローバル変数がこれを保持しています。 ユーザは新しい手続き値を作ることができます。
さらにもうひとつのデータ型はポートです。ポートは これを通じて入出力をおこなうコンジットです。 ポートは通常ファイルやコンソールに結びつけられています。
“Hello, World!” プログラムで、コンソールに文字列を
書くのに手続きdisplay
を使いました。display
は
二つの引数をとることができて、ひとつは表示する文字列、
もうひとつは表示すべき出力ポートです。
さきほどのプログラムでは、display
の二つめの引数は
暗黙のものです。デフォルトの出力ポートが使われていて、
これは標準出力ポートです。現在の標準出力ポートは
(current-output-port)
という手続き呼び出しで得ることができます。
(display "Hello, World!" (current-output-port))
ここで議論している、すべてのデータ型は、あらゆるものを
含むS式とよばれるデータ型にまとめられます
(S)はシンボルを表しています)。つまり、
42
、#\c
、(1 . 2)
、#(a b c)
、"Hello"
、(quote xyz)
、
(string->number "16")
、(begin (display "Hello, World!") (newline))
はすべてS式です。