読者です 読者をやめる 読者になる 読者になる

夢とガラクタの集積場

落ちこぼれ三流エンジニアである管理人の夢想=『夢』と、潰えた夢=『ガラクタ』の集積場です。

Clojure勉強日記(その2 2.1 フォーム

こんにちは。 では、さっそく実際のコードを書いてみましょう。

今回の範囲は「2.1 フォーム」です。
「実際にClojureでどうやって記述するか?」の章で、多分この辺が中途半端だったので途中からわけわからくなっていました。
そのため、地道に。

1.フォーム

Clojureにおいてはリーダがプログラム中の各要素を「フォーム」として読み込み、
フォームごとにClojureのデータ構造に変換することでClojureの内部に取り込む・・・
という動作となります。で、実際にフォームとして使えるのが下記。

1.数値型

普通に「1111」と記述すればOK。
ただ、一定以上大きな値(Long値の限界超過)だと末尾に「N」がついて単なる数値とは違う方として扱われるようです。
BigIntとして扱われているようです。

user=> 1111111111111111
1111111111111111
user=> 2147483647
2147483647
user=> 2147483648
2147483648
user=> 9223372036854775807
9223372036854775807
user=> 9223372036854775808
9223372036854775808N
2.ベクタ
user=> [1 4 6]
[1 4 6]
3.関数呼び出し構文

「+」(半角)も可変長引数を取る関数のようです。
前に来るのが初めて見ると微妙かもしれませんが、plusという関数だったとすると
(plus 2 4 5)と書かれていた方が自然ですからね。
単にそれが計算演算子にも適用されただけのようです。
あとは「可変長引数を簡単に取れる」「引数が指定されない場合も単なる分岐で表現可能」となります。
・・・確かに。

user=> (+ 2 4 5)
11
4.文字列

改行した場合は改行文字が文字列に入るようです。

user=> "test test
testtest"
"test test\ntesttest"

String#splitのように一部呼び出せない関数もある模様。
・・・使い方が誤っているだけかもしれませんが。

user=> (.split "Test Test Test")
IllegalArgumentException No matching field found: split for class java.lang.Stri
ng  clojure.lang.Reflector.getInstanceField (Reflector.java:271)
user=> (String/split "Test Tset")
CompilerException java.lang.IllegalArgumentException: No matching method: split,
 compiling:(NO_SOURCE_PATH:36:1)
user=> (.toLowerCase "Test Test Test")
"test test test"
5.関数適用

文字を交互にリテラルとして取得する関数「interleave」を使うと下記のようになります。

user=> (interleave "Test" "Exam")
(\T \E \e \x \s \a \t \m)
user=> (class (interleave "Test" "Exam"))
clojure.lang.LazySeq
user=> (class "Test")
java.lang.String

LazySeq・・・という形式で返ってくるようです。
で、strは渡された値の「toStringを結合して返す」もののため、
実際に実行してみると下記のようになります。

user=> (class "Test")
java.lang.String
user=> (str (interleave "Test" "Exam"))
"clojure.lang.LazySeq@9f5f5d2c"
user=> (str (interleave "Test T" "Exam X"))
"clojure.lang.LazySeq@f8e3f5b0"
user=> (str (interleave "Test T" "Exam X") (interleave "Test" "Exam"))
"clojure.lang.LazySeq@f8e3f5b0clojure.lang.LazySeq@9f5f5d2c"

今回のような場合、個々の中身に対してstrを実行したい場合はapplyを使う模様。

user=> (apply str (interleave "Test T" "Exam X") (interleave "Test" "Exam"))
"clojure.lang.LazySeq@f8e3f5b0TEexsatm"
user=> (apply str  (interleave "Test" "Exam"))
"TEexsatm"

・・・なんですが、どうやら最後の引数しかargsという塊で扱われないようなので、
途中の引数はそのままtoStringされてしまうようです。ふむむ。

6.ブール値とnil

nullと同じ扱われ方をするオブジェクトとしてnilがある。
但し、Clojureではnullではなく「nilというオブジェクト」という扱いのため、
NullPointerExceptionは発生しないようです。

7.マップ

要素を偶数個並べるとMapになる。奇数個目がKeyで、偶数個目がValue。2個ごとにカンマで区切ってもいい。#がつく。

user=> (def inventors {"Lisp" "McCarthy" "Clojure" "Hickey" })
#'user/inventors
user=> (def inventors {"Lisp" "McCarthy" , "Clojure" "Hickey" })
#'user/inventors
user=> (str inventors)
"{\"Clojure\" \"Hickey\", \"Lisp\" \"McCarthy\"}"

マップ自体も関数となっており、(マップ名 キー)と入力すると結果が返る。
尚、(get マップ名 キー 取得失敗時の値)と入力するとデフォルト値つきgetが実行できる。

user=> (inventors "Test")
nil
user=> (inventors "Lisp")
"McCarthy"
user=> (get inventors "Lisp" "NotFound")
"McCarthy"
user=> (get inventors "Test" "NotFound")
"NotFound"
8.キーワード

Clojureではダブルクォートなどでは無い文字は「評価」される。
だが、先頭に:をつけた場合は評価されず、常に文字列として扱われる。
このような性質を使用してマップのキー(バリューでもいいですが)などに用いるとわかりやすい。

user=> foo
CompilerException java.lang.RuntimeException: Unable to resolve symbol: foo in t
his context, compiling:(NO_SOURCE_PATH:0:0)
user=> :foo
:foo
user=> (def inventors {:Lisp "McCarthy" :Clojure "Hickey" })
#'user/inventors
user=> (inventors :Lisp)
"McCarthy"

キーワードも関数として動作する。
引数としてマップが渡されるとマップの中から要素を抽出する。

user=> (:Lisp inventors)
"McCarthy"
user=> (:Test inventors)
nil
9.構造体

同じキーを持つマップをいくつも作りたい場合defstructで構造体が定義可能。
エンティティクラスと似たような機能を持たせられそう。

user=> (defstruct person :name :age)
#'user/person
user=> (struct person :kimutansk "kimutansk" "XXX")
IllegalArgumentException Too many arguments to struct constructor  clojure.lang.
PersistentStructMap.construct (PersistentStructMap.java:77)
user=> (struct person "kimutansk" "XXX")
{:name "kimutansk", :age "XXX"}

defで作成した構造体を特定の名称に代入可能だが、
その場合は文字列リテラルやキーワードには代入不可。
なぜなら、文字列リテラルやキーワードは「解釈されない」ため。

user=> (def :kimutansk (struct person "kimutansk" "XXX"))
CompilerException java.lang.RuntimeException: First argument to def must be a Sy
mbol, compiling:(NO_SOURCE_PATH:21:1)
user=> (def "kimutansk" (struct person "kimutansk" "XXX"))
CompilerException java.lang.RuntimeException: First argument to def must be a Sy
mbol, compiling:(NO_SOURCE_PATH:22:1)
user=> (def kimutansk (struct person "kimutansk" "XXX"))
#'user/kimutansk

構造体のインスタンスはマップと同じような動作をする。

user=> kimutansk
{:name "kimutansk", :age "XXX"}
user=> (kimutansk :age)
"XXX"
user=> (:age kimutansk)
"XXX"

defstructによる構造体定義は「こういうキーを持つ構造体を作る」だけで
別にキーに対応した値が無くても問題としない。
既存の構造体にさらにキーを追加してマップを作成したい場合はstruct-mapを使用する。

user=> (struct-map person :test "Test")
{:name nil, :age nil, :test "Test"}
user=> (def testPerson (struct-map person :test "Test"))
#'user/testPerson
user=> testPerson
{:name nil, :age nil, :test "Test"}

少し上でエンティティ云々と書いてしまいましたがそれは誤りで、
「構造体=決まったキーを持つマップ」というのが正しい実態のようです。

ともあれ、実際に読んで、コードを書いて、ここに書く・・・と3重で行うとそれなりにのみこめますね。
とりあえずこれで2.1は一通り改造しながら書いたため、次は2.2でしょうかねぇ。