Friday, June 22, 2012

Clojure入門「データ構造」

こんにちは。清水です。

今回はClojureのデータ型についていくつかだらだら解説します。

ClojureのデータにはJavaの標準なクラスのインスタンスになっているものがいくつかあります。なにがどんなクラスのインスタンスなのかは値をclass関数に渡したりするとわかります。

列挙系はダレますね。。。

文字列

Clojureの文字列はJavaのStringオブジェクトです。

"Hello, world."

ダブルクォーテーションで囲んだものが文字列になります。str関数を使うとオブジェクトを文字列に変換できます。

(str 428) ; => "428"

strは複数の引数を受け取り、全てを結合した文字列を返します。

(str "Hello" "World") ; => "HelloWorld"

clojure.stringという名前空間にいくつか文字列用の関数が用意されています。Stringのメソッドを使うこともできます。

文字

文字列とは別にひとつの文字を表すデータ型もあります。

\A

文字の前にバックスラッシュを付けると、その文字のリテラルになります。これはJavaのCharacterオブジェクトです。

\space ; 半角スペース\newline ; 改行\tab ; タブ

など、特殊な文字を表すリテラルもあります。

数値

数値には整数と浮動小数点数と分数があります。

4287.222/3

整数は値の大きさによってInteger、Long、BigIntegerオブジェクトのいずれかになります。浮動小数点数はDoubleオブジェクトで、分数はClojure独自のクラスのインスタンスです。また、分数のリテラルは/の前後にスペースを入れることはできません。

ブール

ブール値はJavaのBooleanオブジェクトで、trueとfalseの二つの値があります。ifの条件式や、コレクションをフィルタリングする時に渡す関数の戻り値などに使われます。ただし、その場合は厳密にtrueかfalseである必要はなく、falseと(後述する)nil以外は全てtrueとして扱われます。0や空の文字列や空のリストなどはfalseにはなりません。

nil

nilは何もないことを表す値です。内部的にはJavaのnullです。nilはfalseとして扱われる他、空のコレクションにもなります。

ちなみに、空のリストはnilではありません。

キーワード

キーワードは名前空間と名前を持つオブジェクトです。文字列にも似ていますが、同じ名前空間と名前を持つキーワードは同じインスタンスになるため、文字列よりも比較が速い等の利点があります。

:foo

上記のようにコロンの後に名前をつけると、その名前のキーワードになります。この形式のキーワードは名前空間を持ちません。そのため、どの名前空間内で記述しても同じインスタンスを表すことになります。

::foo

このようにコロンを二つ付けると、現在の名前空間のキーワードになります。

:xyzzy/foo

また、上記のように名前空間と名前をスラッシュで区切ると、任意の名前空間のキーワードを使うことができます。

キーワードはマップのキーに使われることが多いです。ほとんどの場合、名前空間を持たないキーワードが使われ、名前空間毎に固有のキーを用意したい場合などに名前空間付きのキーワードを使ったりします。

(Rubyのシンボルに近いのかも。)

シンボル

シンボルはキーワードと同じように名前空間と名前を持ち、コード上でなにかの名前を表すのに使われます。変数や関数の名前、strやprintlnなどの名前もシンボルです。シンボルはそのまま評価すると結びつけられた値を返します。そのため、

str

と書けばstrの関数自体が返されます。定義されていないシンボルを評価しようとすると

java.lang.Exception: Unable to resolve symbol: foo in this context

などといったエラーが発生します。シンボルを直接値として扱うには、名前の前にシングルクォーテーションを付けます。

'foo

このシンボルは名前空間を持ちません。シングルクォーテーションの代わりにバッククォーテーションを付けると、現在の名前空間内のシンボルになり、キーワードと同じように名前の前に名前空間を付けると任意の名前空間のシンボルになります。

ちなみにこのシングルクォーテーションはquoteというマクロの糖衣構文で、下のコードと同じになります。

(quote foo)

quoteは引数を評価せずに式としてそのまま返します。

シンボルはマクロを書くときに使います、それ以外では使う機会は少ないです。

ちなみに、キーワードはシンボルではなく、シンボルはキーワードではありません。

リスト

Clojureのリストは連結リストとして実装されたオブジェクトです。JavaのListインタフェースを実装しているため、Javaのメソッドに渡すこともできます。リストは丸括弧に値を並べた形で表されます。

'(15 9 48)

リストを作るときには先ほどのquoteを使います。quoteしないと上記の場合は15という関数を呼び出すという意味になってしまいます。上記のように書いた場合は括弧の中のものもquoteされてしまうので、変数の値をリストに含めようとするとシンボルになってしまいます。変数の値を含めたい場合はlist関数を使います。

(list foo bar xyzzy)

この場合は変数foo、bar、xyzzyの値を含んだリストになります。空のリストの場合のみ、quoteもlistもなく、()と書くだけで作れます。

リストの先頭の要素はfirst関数、先頭を除いたリストはrest関数で取得できます。

(first '(15 9 48)) ; => 15(rest '(15 9 48)) ; => (9 48)

ベクター

ベクターは整数のインデックスでアクセスできるコレクションです。JavaのVectorとは違うものですが、Listインタフェースを実装しています。ベクターは[]の中に要素を並べて書きます。

[15 9 48][foo bar xyzzy]

ベクターを作るときにはquoteする必要はなく、変数もそのまま書けます。

要素を取得するにはget関数を使います。

(get [15 9 48] 1) ; => 9

該当する要素が無い場合はnil、またはgetの3番目の引数を返します。また、ベクターそのものを関数として呼び出しても要素を取得することができます。

([15 9 48] 1) ; => 9

この形式の場合、該当する要素が無い場合はエラーになります。

マップ

マップは順序を持たない連想配列です。{}の中にキーと値を並べて書きます。

{:name => "Hamachi" :price => 130}

上記の場合はnameとpriceという名前のキーワード二つをキーに持つマップになります。

マップの要素を取得するのにもget関数が使えます。

(get {:name => "Hamachi" :price => 130} :price) ; => 130

ベクター同様に、マップそのものを関数として呼び出して要素を取得することもできます。また、キーワードかシンボルを関数としてマップを渡しても同様のことができます。

({:name => "Hamachi" :price => 130} :price) ; => 130(:price {:name => "Hamachi" :price => 130}) ; => 130

この形式の場合でも該当する要素が無い場合はnilを返しますが、関数として呼び出せないものを左側に置いてしまうとエラーになるので注意が必要です。

マップはJavaのMapインタフェースを実装しているので、メソッドの引数に渡したりできます。Javaで書くよりも簡単。


セット

セットは順序を持たず重複の無い要素の集合です。JavaのSetインタフェースを実装しています。セットは#{}の中に要素を並べて書きます。

#{10 20 30}

セットにもget関数が使えます。引数として要素を渡すと、その要素がセットの中に含まれる場合には同じものを返し、無い場合はnil(または3番の引数)を返します。

(get #{10 20 30} 20) ; => 20

セット自体を関数として呼び出しても同じことができます。

(#{10 20 30} 20) ; => 20

要素がある場合には要素そのものを、無い場合はnilを返すので、セットがfalseやnilを含むことが無ければ条件式として使うことができます。

上記のリストやベクターなどのコレクションは全て変更不能なオブジェクトです。ベクターの要素を変えたい場合は要素を変えた新しいベクターを作成します。Clojureにはその為の関数が用意されています。次回はコレクションについてもうちょっと解説する予定です。

なにやら新しいシリーズが始まったようなので合わせてどうぞ

2 comments:

  1. こんにちは.Clojureの入門記事を探していましたので,とても参考になっています.
    ところで,マップの説明の
    {:name => "Hamachi" :price => 130}
    は,
    {:name "Hamachi" :price 130}
    の入力間違い,ということでよいでしょうか?

    =>があると見やすいような気もしますので,
    何か(処理系に?)工夫をされているのでしょうか?

    ReplyDelete
  2. ありがとうございます。=>は単純に間違いですね。

    ReplyDelete