データ型

データ型は関連する値の集まりです。この集まりは、互いに素である 必要はなく、多くの場合には階層構造になります。Schemeには豊富なデータ型 があり、いくつかのものは、単純(分割不可能)なもので、それ以外は他のデータ型 を組み合せて合成したデータ型です。

2.1  単純データ型

Schemeの単純データ型には、真理値、数値、文字、シンボルがあります。

2.1.1  真理値

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ではない値はすべて真の値として扱います。

2.1.2  数値

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は 算術演算や三角関数の大規模で包括的な一式を提供します。 たとえば、atanexpおよびsqrtは、それぞれ、 その引数の逆正接、自然逆対数および平方根を返します。 詳細はR5RS[cite{r5rs}]を参照してください。

2.1.3  文字

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

2.1.4  シンボル

上でみた単純データ型はどれも自己評価的です。すなわち、 これらのデータ型のうちどのオブジェクトをリスナーに入力しても リスナーによって返される評価結果は、入力したものと同じになるでしょう。

#t  =>  #t
42  =>  42
#\c =>  #\c

シンボルの振舞いは同じではありません。シンボルは Schemeのプログラムでは変数識別子として使われる ので、評価されて、その変数がもつ値になるからです。それでもなお、 シンボルは単純データ型です。シンボルは正当な値で、文字や数値 その他の値と変換可能なものです。

Schemeによって変数と解釈されないシンボルを指定するには シンボルをクウォートしなければなりません。

(quote xyz)
=>  xyz

このタイプのクウォートはSchemeでは良くつかうので、簡略記法が 用意されています。式

'E

はSchemeでは以下と同じです。

(quote E)

Schemeのシンボルは文字列により名前がついています。 シンボル名の唯一の制限は他のデータ(つまり、文字、真理値)と取り違えるよう なものではいけないということです。それゆえ、this-is-a-symboli18n<=>$!#*はすべてシンボルで、16-i (複素数!) #t"this-is-a-string"(barf)(リスト)はシンボルではありません。 シンボルかどうかをチェックするには述語symbol?を使います。

(symbol? 'xyz) =>  #t
(symbol? 42)   =>  #f

Scheme のシンボルは通常、大文字と小文字を区別しません。 したがって、Caloriecalorieは等しいシンボルです。

(eqv? 'Calorie 'calorie)
=>  #t

シンボルxyzをフォームdefineをつかってグローバル変数として 利用することができます。

(define xyz 9)

これは、変数xyzが値9を保持するといっています。xyzを リスナーに食わせると結果はxyzの保持するその値になります。

xyz
=>  9

変数が保持している値を変更するには、フォームset!が使えます。

(set! xyz #\c)

とすると

xyz
=>  #\c

2.2  合成データ型

合成データ型は構造化された方法で他のデータ型から値を合成することによって 構築されます。

2.2.1  文字列

文字列は文字の並び(名前として文字のならびをもつ単純データ、シンボル と混同してはいけません)です。一連の文字の並びを二重引用符で囲むことで 文字列を指定できます。文字列は評価すると、その文字列自身になります。

"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?です。

stringmake-stringstring-appendの結果として 得られた文字列は可変です。手続きstring-set!は与えられた インデックスの文字を置き換えます。

(define hello (string #\H #\e #\l #\l #\o)) 
hello
=>  "Hello"

(string-set! hello 1 #\a)
hello
=>  "Hallo"

2.2.2  ベクタ

ベクタは文字列のような並べですが、その要素は文字以外のなにでも かまいません。ベクタ自身でもかまわないので、これは多次元ベクタを 作るよい方法です。

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?です。

2.2.3  ドット対とリスト

ドット対は二つの任意の値を組合せて順序対にすることで 作りだされた合成値です。最初の要素はcar、二番目の要素は cdrと呼ばれ、組合せの手続きはconsです。

(cons 1 #t)
=>  (1 . #t)

ドット対は自己評価的ではありません。そこで、データとして 直接(すなわちcons呼び出しによって作りだすことなく)指定するには 明示的にクウォートしなければなりません。

'(1 . #t) =>  (1 . #t)

(1 . #t)  -->ERROR!!!

アクセサ手続きはcarcdrです。

(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)

このリストのcarcar1です。 そして、carcdr2です。すなわち、

(car (car y))
=>  1

(cdr (car y))
=>  2

Schemeはcarcdr手続きのカスケードした組み合わせに 対応する省略形手続きを提供しています。つまり、caarcarcarを、cdarcarcdrを表わします。

(caar y)
=>  1

(cdar y)
=>  2

c...rスタイルの省略形は4段のカスケードまでは存在することが 保証されています。つまり、cadrcdadrcdaddrはいつでも 使えます。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

2.2.4  データ型間の変換

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->stringvector->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

2.3  そのほかのデータ型

Schemeには、そのほかのデータ型がいくつかあります。 ひとつは手続きです。すでにいろいろな手続きを見ました。 たとえば、display+consです。実際、これらは 手続き値をもつ変数です。値そのものは数値や文字のように、 目に見えるものではありません。

cons
=>  <procedure>

いままで見てきた手続きはプリミティブ手続きで、標準的な グローバル変数がこれを保持しています。 ユーザは新しい手続き値を作ることができます。

さらにもうひとつのデータ型はポートです。ポートは これを通じて入出力をおこなうコンジットです。 ポートは通常ファイルやコンソールに結びつけられています。

“Hello, World!” プログラムで、コンソールに文字列を 書くのに手続きdisplayを使いました。displayは 二つの引数をとることができて、ひとつは表示する文字列、 もうひとつは表示すべき出力ポートです。

さきほどのプログラムでは、displayの二つめの引数は 暗黙のものです。デフォルトの出力ポートが使われていて、 これは標準出力ポートです。現在の標準出力ポートは (current-output-port)という手続き呼び出しで得ることができます。

(display "Hello, World!" (current-output-port))

2.4  S式

ここで議論している、すべてのデータ型は、あらゆるものを 含むS式とよばれるデータ型にまとめられます (S)はシンボルを表しています)。つまり、 42#\c(1 . 2)#(a b c)"Hello"(quote xyz)(string->number "16")(begin (display "Hello, World!") (newline)) はすべてS式です。