3.3. リストにリストもそのまたリスト

変数と関数の鍛錬をしたばかりですが、 ここからは Scheme のリストの霧靄立ち籠める湿地に足を踏み入れます。

3.3.1. リストの定義法

リストについてあれこれ話す前にアトム値とリストの何が違うのか知っていただく必要があります。

アトム値についてはこれまでの講義で変数を初期化するときにご覧に入れました。 アトム値とは単独値のことです。 ですから例えばつぎの構文で変数xに単なる値 8 を代入できます。

(let* ( (x 8) ) x)

この例では最後にもう一度 x とだけ書いた式をつけ加えました。 これは変数 x に最終的に与えられた値を表示させるのが目的なのですが、 普段ははこんなことをしなくても済みます。 というのも let* でも最後の構文は得た値を返すようになっているためこの式自体が関数のようにふるまえるからです。

変数は単なる値のみならず一連の他の値をまとめたリストを指すこともできます。 値 1、 3、 5 からなるリストを変数 x に代入するにはこのように書きます。

(let* ( (x '(1 3 5))) x)

先の構文とこの構文をおのおの Script-Fu コンソールに打ち込んでどんな結果が返されるか試してみましょう。 先の構文を打ち込んだ場合はこのような簡単な答えになります。

8

ところがもう一方の構文を打ち込んだ場合にはつぎのような結果を返します。

(1 3 5)

返された値が 8 の例は変数 x がアトム値 8 をもつことを示しています。 もう一方の (1 3 5) が返された例では変数 x には単なる値ではなく一連の値のリストが入っていたことを示します。 リストをつくるときコンマで区切るようなことはありませんし、 返ってきた結果にも空白以外の区切り文字がないことに注目しましょう。

リストを定義する構文は、

'(a b c)

のように書く規則になっており、 abc はリテラル (値の直接表記) です。 これらをまとめた丸括弧の前にアポストロフィ'をつけるとその括弧は関数や式ではなくリテラルだけで構成されたリストであることを示します。

空のリストもつぎのようにすれば定義できます。

'()

もしくは単純に、

()

と書いてもよろしい。 リストにはアトム値だけでなく他のリストも入れられます。

(let*
   (
        (x
           '("GIMP" (1 2 3) ("is" ("great" () ) ) )
        )
    )
    x
)
      

ちなみに最初にアポストロフィをつけてしまえば、 その内部でさらにリストをつくるときにアポストロフィを省略できます。 それでは直ちに Script-Fu コンソールに上記のリストを写してどんな結果がでるか見てみましょう。

返される値が単なるアトム値のリストではないことに注意してください。 ここではリテラル "GIMP" やリスト (1 2 3) などからなるリストができています。

3.3.2. リストの考え方

リストを先頭後続からできているととらえるのはうまい方法です。 先頭とはリストの最初の要素のことであり、 後続はリストの残りの要素のことです。 リストから要素をとりだしたり加えたりする方法を学ぶなかでこうした捉え方の重要性が分かるはずです。

3.3.3. 連結でリストを作成 (Cons関数)

今後使うことになる関数のなかでも cons は多く目にすることになる関数です。 この関数がとる変数のひとつはリストであり、 第2引数として与えます。 さきほどリストのことを先頭の要素と後続の要素群としてとらえてみるよう勧めました。 それが cons のやっていることです。 この関数は要素をリストの先頭に差し込みます。 それではつぎのようにリストを作ってみましょう。

(cons 1 '(2 3 4) )

結果的にリストは (1 2 3 4) となります。

同様にして要素がひとつだけのリストも作れます。

(cons 1 () )

先に定義してあった変数が、 あらゆるリテラルの代わりに思い通りに使えます。

3.3.4. list 関数を使ってリストを定義

リテラルとあらかじめ定義しておいた変数を組み合わせて、 list 関数を使ってリストを定義できます。

(list 5 4 3 a b c)

この式は変数 abc それぞれがもつ値を含むリストを組み立てて返します。 たとえば、

        (let*  (
                  (a 1)
                  (b 2)
                  (c 3)
               )

               (list 5 4 3 a b c)
        )
      

この式はリスト (5 4 3 1 2 3) を作成します。

3.3.5. リスト内の値を引き出す

リストの中にある値を取り出すときは car 関数と cdr 関数を使います。 前者がリストの先頭の要素を返し、 後者はその残りを返します。 先に述べたとおり両関数はリストを先頭と後続の構成に分解します。

3.3.6. car 関数

car はリストの最初の要素 (リストの先頭) を返します。 リストが空ではいけません。 つぎの式はリストの先頭の要素を返します。

(car '("first" 2 "third"))

すなわちこれはつぎの値と同じです。

"first"

3.3.7. cdr 関数

cdr はリストから先頭の値を除外した残りの要素のリスト (リストの後続) を返します。 リストにひとつしか要素が入っていなかった場合は空のリストが返されます。

(cdr '("first" 2 "third"))

この式が返すのはつぎのとおりです。

(2 "third")

また、

(cdr '("たったひとつ"))

この式が返すのはつぎのとおりです。

()

3.3.8. リストの他の位置の要素を引き出す

はい、 もう十分です。 リストから先頭の要素も後続の要素群も取り出せるようになりました。 それではリストの2番目や3番目などほかの要素はどんなふうにしたら取り出せるのでしょう。 ここで使える便利な関数があります。 たとえば先頭の先頭を引き出すには caadr が、 後続から後続の要素群を引き出すには cddr が使えるというふうになっています。

根本的に命名規則は簡単です。 a や d がそれぞれリストの先頭や後続を表しているのです。 ですから

(car (cdr (car x) ) )

という式はつぎのように書き直せます。

(cadar x)

リスト用引き出し関数の練習のためつぎのスクリプトを書いてみましょう。 コンソール上で試すときは1行につづけて書きます。 そうしたら carcdr の両関数をいろいろ組み合わせてリスト内の要素群を取り出す式を作ってみましょう。

        (let* (
                 (x  '( (1 2 (3 4 5) 6)  7  8  (9 10) )
                 )
              )
              ; この行以降にご自分なりの式を car や cdr を駆使して書きます
        )
      

両関数だけをいろいろ組み合わせて、 リストから数字の3を引き出す式を書きましょう。 これができるならあなたは Script-Fu の達人になりつつあるのです。

[注記] 注記

Scheme 言語ではセミコロン (;) がコメントを表しています。 この記号とここから行の終わりまでは何を書いてもスクリプトのインタープリタが無視しますので、 この方法でスクリプトにコメントをさしはさみ、 あとで読むときの記憶の助けに利用しましょう。