2010年3月27日土曜日

Clojure始めました2

Clojureをさわり始めたので、メモ。

;;空白文字が入る箇所に,(カンマ)を入れても良い。

;;rangeは[end] [start end] [start end step]の3パターンで利用できる.
;;start(デフォルトは0)からstep(デフォルトは0)ずつend未満の値を集める
(print (range 0 10))
|(0 1 2 3 4 5 6 7 8 9)
(print (range 0 10 2))
|(2 4 6 8)

;;mapはシーケンスの各要素を引数として関数を呼び出した結果を集めて返す。
;;無名関数はfnで作成できるが、省略記法として#(hoge %)のように
;;作成することもできる。この時、%は第1引数を、%nは第n引数を表す。
(map #(* % %) (range 10))

;;同様の処理はforでは以下のように書ける。
;;forはループではなくリスト内包表記というらしい。
;;CLと違い、forやlet,defnなどで変数束縛や仮引数を書く場所は
;;丸括弧()ではなく角括弧[]で括る。
(for [x (range 10)] (* x x))
->(0 1 4 9 16 25 36 49 64 81)

;;forにはwhenやwhileなどのキーワードを指定して式を評価する条件を与える
;;ことができる。
;;whenは条件が真の場合のみ本体を評価して値を集める。
;;whileは条件が真の間本体を評価して値を集め、条件が偽になった時点で終了する。
(for [x (range 10) :when (odd? x)] x)
->(1 3 5 7 9)
(for [x (range 10) :while (< x 5)] x)
(0 1 2 3 4)

;;forにキーワードwhenを与えた場合と同じような動作はfilterで行える。
(filter odd? (range 10))
->(1 3 5 7 9)

;;forは変数束縛(?)を複数指定出来る。
;;並行に束縛されるのではなく、多重ループのような順序で束縛される。
;;後ろに書いた変数ほど先に束縛が繰り返される。
(for [x "abc" y "ABC"] (str x y))
->("aA" "aB" "aC" "bA" "bB" "bC" "cA" "cB" "cC")

;;Scheme等でネタにされた'Lisp脳'的FizzBuzzは以下のように書ける。
;;condはCLなどと異なり、条件式と真の時の動作を括弧では括らず順番に書く。
;;:elseの箇所は偽以外ならなんでも良いと思う。
;;(rem a b)はa/bの余りを返す。remainderの略だと思う。
(defn fizzbuzz []
(map
#(cond
(zero? (rem % 15)) "FizzBuzz"
(zero? (rem % 5)) "Buzz"
(zero? (rem % 3)) "Fizz"
:else %)
(range 1 31)))

;;シーケンスは遅延評価されるため無限長のシーケンスを扱える。
;;takeでシーケンスの要素を先頭から指定した個数分取り出せる。
;;シーケンスは変更不可能なので、シーケンスに対する処理を行うと新しいシーケンスが作られている。
;;リストのように丸括弧で表示されていても、シーケンス操作の返り値の実際のクラスはシーケンスである。
'(1)
->(1)
(class '(1))
->clojure.lang.PersistentList
(map (fn [x] x) '(1))
->(1)
(class (map (fn [x] x) '(1)))
->clojure.lang.LazySeq

;;マップ(hash-map)はキーと値のペアを並べたもの。
;;関数として扱うこともでき、その場合は引数にキーを取り、対応する値を返す。
({1 2 3 4 5 6} 3)
->4

;;キーワードは、マップからそのキーワードに対する値を取り出す関数でもある。
(:a {:a 2 :b 3})
->2

;;ベクタも関数として扱うことができ、その場合は引数にインデックスを取る。
([1 2 3] 0)
->1

;;関数定義にはdefnを用いる。
;;mapcatはCLのmapcanのように各要素に関数を適用した後のリストを
;;つなげ合わせて返すようだ。
(defn flatten [tree]
(mapcat
#(if (list? %)
(flatten %)
(list %))
tree))

(flatten '(1 2 (3 4)))
->(1 2 3 4)

;;リスト(というかシーケンス)の長さを返すにはcountを用いる。
;;自前で実装すると、例えば以下のようになる。
;;CLでは両方あるけれど、Clojureにはcar/cdrは存在せず、first/restのみ利用できる。
;;また、nilは空リストでは無いので気をつける。ex) (nil? ()) => false
(defn length [lst]
(if (empty? lst)
0
(+ 1 (length (rest lst)))))

;;ClojureではJavaの仕様上末尾再帰を最適化しないらしい。
;;かわりにrecurを利用すると関数の初め(またはloop)に飛ぶ。
;;相互再帰はtrampolineで行える。
;;末尾再帰よりも遅延シーケンスを利用するのがClojure流らしい。
;;関数は引数の個数によって動作を変える事が出来る。
;;仮引数と関数本体を括弧で括ったものを列挙すれば良い。
(defn length-tail
([lst] (length-tail lst 0))
([lst acc]
(if (empty? lst)
acc
(recur (rest lst) (+ 1 acc)))))

0 件のコメント:

コメントを投稿