暇を持て余しているJava廃人はClojureで遊ぼう
準備
- ClojureDocs (API、サンプルコード参照)
- Clojure Japanese Documentation (日本語ドキュメント。詳しく知りたいときに)
- Light Table (エディタ、実行環境)
- Leiningen (Mavenのようなもの。あとでいい)
Light Table 動作確認
- プロキシ環境の場合、環境変数HTTP_PROXYとHTTPS_PROXYを事前に設定
- 新しいファイルを作成
- ctrl-space
- コマンドバーが開く
- syn
- Syntax設定
- clo
- Clojure選択
- エディタで適当にコード入力
- (map - (range 10))
- ctrl-enter (cmd-enter)
- コード実行
- 関数実行を止めたいときは、ctrl-space後、cancelかdisconnectでコマンド探す
基本のデータ型
- 有理数のリテラルがある(2/3など)
- 基数(radix)として2〜36まで指定できる数値リテラルがある(2r101、36rxyZなど)
- 整数はLong、浮動小数点数はDoubleになる
- NをつけるとBigI n t、MをつけるとBigDeci m al
- 項目はスペースで区切る。カンマはスペースと同じ
- 文字列の連結は
str
で。\a
などで文字を表す
- 特殊な文字として\return \newline \tab \space \backspace \formfeedがある
- defで変数定義(Clojureは基本的にイミュータブルなので変更されないけど)
- 文字列の一致比較は
=
でできる
- 文字列と似て非なるものとしてキーワードがある(Rubyにあるシンボルのようなもの)。
:abc
のようにコロンを先頭につける - 文字列と違い、連結や切り出しはできない。また文字列は文字のシーケンスとして扱えるが、キーワードはシーケンスではない
シーケンス、コレクション
- リスト(LinkedListのようなもの)
(1 2 3)
- ベクタ(ArrayListのようなもの)
[2 3 4]
- マップ(HashMapのようなもの。LinkedHashMapではない)
{:a 1 :b 2}
- セット(HashSetのようなもの。LinkedHashSetではない)
#{:a :b :c}
- これらはシーケンスという抽象化した構造として様々な関数で統一的に扱うことができる。
- マップはclojure.lang.MapEntryのシーケンスとなる。MapEntryはclojure.lang.IPersistentVectorであるためベクタでもあり、java.util.Map.Entryでもある
- なお、Iterableもシーケンスとして扱える
シンボルとクオート、評価
- リストは中身が1つ以上のときに評価されると、先頭の要素が関数として扱われるため
(1 2 3)
のようなリストを実行(評価)すると例外が発生する
- 例外を発生させないためにはクオートをつけて、評価を1回行わないようにすることができる
-
(str :a :b)
のようなコードのstr
の部分はシンボル(clojure.lang.Symbol)であり、シンボルが関数として評価されると現在の環境から解決された(特定された)関数が実行される -
クオートをつけると、その部分全体の評価が行われないので
(def v 9) '[1 v]
の結果は、[1 9]
ではなく[1 v]
になるというところに注意
変数と関数の定義
- 関数は
defn
で定義できる -
:pre
と:post
で複数個の事前条件と事後条件(%
が戻り値を表す)を指定できる
- 関数では複数の引数定義を行える
-
&
の後ろは可変長引数を表す
- privateな関数は
defn-
で、privateな変数はdef ^:private
で定義できる - 関数内のローカル変数は
let
で定義できる
Javaメソッド呼び出し
-
(Math/pow 2 3)
のようにjava.langパッケージにある(Clojure 1.6.0ではJava 6に存在する)クラスのstaticメソッドはimportなしに呼び出せる - インスタンスメソッドはメソッド名の前に
.
をつけて呼び出せる - importをするとjava.lang以外のパッケージのクラス名を(パッケージ名つけずに)使える
- クラス名の後に
.
をつけるか、new
を使うことで新しいオブジェクトを構築できる
- インスタンスメソッドは
.
を使っても呼び出せる -
..
を使うとメソッド呼び出しを連鎖できる -
doto
を使うと同じオブジェクトに対する複数回メソッド呼び出しが簡単にできる
filter, map, reduce, for
-
filter
とremove
は逆の動作をする -
fn
で無名関数を作れる
-
#(...)
の形式でも無名関数を作れる。%
または%1
が第一引数、%2
が第2引数、%&
は残りの引数を表す -
keep
はmap
と似ているが、値がnilとなる結果は含まれない。
-
reduce
には初期値を与えることもできる -
reduce
を途中で止めたい場合はreduced
を使える -
if
やwhen
などの条件で"真"と判定されるのはfalse
とnil
以外のすべて
- 多重ループ的な処理をしたい場合は、
for
が便利 -
:while
は入れる場所によって動作が変わるので注意
- 結果をベクタにする
filterv
,mapv
もある
スレッディングマクロ
-
->>
,->
,as->
などを用いることで、深いネスト構造の式をフラットに記述することができる
副作用と遅延シーケンス
- 関数本体や
let
本体など、複数の式が書ける場所ではprintln
などの副作用がある式をそのまま挟み込める -
do
を使えば、どこにでも副作用がある式を入れられる。(do
全体の結果は最後の式の結果となる)
-
println
によってConsoleには以下のような表示がされる
- Javaのオブジェクトに対する操作をするときもそうだが、Clojureのオブジェクトでも
atom
,ref
,with-local-vars
などを用いて副作用がある操作をすることがある - Light Tableで式の途中の値が知りたい場合は、Watchの機能が使える
-
map
やfilter
などの関数では結果は遅延シーケンスとして返されるため、スレッドローカルな値を扱う関数と組み合わせると期待した値にならないことがある
- 遅延シーケンスを即時評価するには
doall
を使うか、ベクタやマップに変換するなどの手が使える
- 副作用だけが必要な場合は
doall
の代わりにdorun
が使える - 無限(遅延)シーケンスを評価するときには
take
などでその一部を取得する
分配束縛
-
let
やdefn
など変数に値を束縛するところでは分配束縛ができる -
:as
を使うとそのベクタ全体の値を束縛できる。(defn
直下のベクタでは:as
は使えない)
-
:keys
を使うとキーワード引数のような関数呼び出しを扱える。:or
でデフォルト値を指定できる
-
:keys
の代わりに:strs
と:syms
で文字列とシンボルも扱えるがあまり使われない(?)
ネームスペース
-
ns
でネームスペースを定義できる。:require
でClojureのネームスペースを利用。:import
でJavaのパッケージを利用
入出力と正規表現
-
spit
とslurp
で文字列の書き込み、読み込みができる -
:append
などのオプションを指定できる。:encoding
でエンコーディング指定可能(デフォルトはUTF-8)
- clojure.java.ioの
writer
とreader
を使うとBufferedWriter, BufferedReaderでの処理ができる - 確実にcloseするために、
with-open
を使用している -
line-seq
は遅延シーケンスを返すため、with-open
の外まで遅延したまま持って来れないことに注意
-
re-find
とre-seq
で正規表現マッチができる。 - 大文字小文字の区別を排除するには(java.util.regex.Patternで定義済みの)
(?i)
などの埋め込みフラグ表現を使う
マクロ
- 制御構造の操作を伴う処理をしたい場合などにマクロを定義する
- マクロの本体にシンタックスクオート
`
をつけ、マクロの引数にアンクオート~
をつけるのがまずは簡単な作り方
- マクロの展開結果を知りたいときは、
macroexpand-1
などを使う - Light Tableのkeymapに
"ctrl-e" [(:eval.custom "(macroexpand-1 '__SELECTION__)")]
などを登録すると選択箇所のマクロ展開に便利
- マクロ本体で変数を使う場合、変数名の後に
#
をつけてユニークな名前にする。(gensym
される) - スプライシングアンクオート
~@
を使うとリストが展開されてからアンクオートされる
Leiningenの使用
- REPLの起動
lein repl
-
(doc map)
や(source filter)
でドキュメントやソースを表示 - Light Tableでソース見たいときはkeymapに
"alt-s" [(:eval.custom "(with-out-str (clojure.repl/source __SELECTION__))" {:verbatim true})]
などを登録すると便利
-
- プロジェクトの作成
lein new app myproject1
- lein new
テンプレート名
プロジェクト名
の形式。テンプレート名
は省略するとデフォルトのテンプレート(ライブラリ用?)が使用される。app
はアプリケーション用 - 生成されたproject.cljの
:dependencies
で依存するライブラリを記述(Maven, Clojarsリポジトリから自動ダウンロード)
- lein new
- standalone.jarの作成
lein uberjar
- (Clojureのjarも含む)すべての依存ライブラリをまとめた単独実行可能なjarファイルを作成する
- ヘルプ
lein help
lein help new
lein help uberjar
...
よく使う関数
よく使う関数で説明をあまりしてないものはこんなところでしょうか。
- apply
- cond, if-let, when-let
- some, every?
- first, second, last
- cons, concat, list*
- mapcat, map-indexed, keep-indexed
- repeat, repeatedly, cycle
- interleave, partition
- identity, comp, partial
- take, take-last, drop, drop-last
他にもよく使う関数あるけど、まず取っ掛かりとしてこれだけわかればClojureで簡単なプログラム書けるでしょう〜