2011-12-25

fill-queueでお手軽非同期処理

この記事はClojure Advent Calendar 2011の25日目の記事として書いています。
あんまりホリデイっぽくないので、Overtoneの記事と入替えて読むとちょうどいいですよ。

前にClojureのシーケンス操作関数の使い方一覧を書いたけど、最後に例示を挙げられなかったものがふたつ残りました。
seq-onは「何かをシーケンスとして扱いたいとき」というわり合い特殊なものなので良いとして、fill-queueはもう少し便利に使えそうなものなので何とか例示ができないかなーと思って簡単なファイルの変更監視スクリプトを書いてみました。

まずはfill-queueのdocを見てみましょう。
user=> (doc fill-queue)
-------------------------
clojure.contrib.seq/fill-queue
([filler-func & optseq])
  filler-func will be called in another thread with a single arg
  'fill'.  filler-func may call fill repeatedly with one arg each
  time which will be pushed onto a queue, blocking if needed until
  this is possible.  fill-queue will return a lazy seq of the values
  filler-func has pushed onto the queue, blocking if needed until each
  next element becomes available.  filler-func's return value is ignored.
nil

どうやら別スレッドで実行させる関数filler-funcを指定して、その関数でゴニョゴニョした値をqueueシーケンスとして受け取れるということらしい。filler-func内から値を返すときは引数fillに渡す。
非同期処理をシーケンスの流儀で取り扱えるっていうやつみたいですね。

これだけだとイメージが沸かないので使用例をぐぐってみたらこんな記事が見つかりました。
The Infolace Story: Simple webhooks with Clojure and Ring
この海外記事を読むとRingサーバー処理になにかフックさせるときに使ってる。途中の図がわかりやすい。

これでも使用例としてはいいんですけど、もうちょっと手元でいじれる例が欲しいのでファイルの最終更新日時を監視するスクリプトを書いて見ました。

;; fill-queue による非同期フックの実験
;; 指定したファイルの最終更新日時が変わったらキューにそのファイル名をプッシュする
(ns watch-file-status
(:use [clojure.contrib.seq :only (fill-queue)])
(:import [java.io File]
[java.util Date]
[java.text SimpleDateFormat]))
(defn- last-modified [filename] (.lastModified (File. filename)))
(defn- thread-name [] (.getName (Thread/currentThread)))
(defn modified-status
"ファイル名をキー、その最終更新日時を値にしたマップを返す"
[filenames]
(reduce into (map #(hash-map % (last-modified %)) filenames)))
(defn watching-loop [fill filenames]
(let [updater (partial modified-status filenames) ; update function
last (atom (updater))]
(println "watching-loop thread:" (thread-name))
(loop [now (updater)]
(let [mods (filter #(> (now %) (@last %)) filenames)]
(if-not (empty? mods)
(do (doseq [f mods]
(fill f)) ; hook
(reset! last now))))
(Thread/sleep 1000) ; interval 1 sec
(recur (updater)))))
(defn show-file-status [filename]
(let [dateformat (SimpleDateFormat. "yyyy-MM-dd HH:mm:ss")]
(println "modified:" filename)
(println "datetime:" (.format dateformat (last-modified filename)))))
;; main
(let [args *command-line-args*]
(println "watching..." args)
(println "main thread:" (thread-name))
(doseq [changed-file (fill-queue (fn [fill] (watching-loop fill args)))]
(show-file-status changed-file)))


watching-loopがfiller-funcで実行される本体です。ループで1秒ごとにファイルの最終更新日時を監視、変わっていたらfillにプッシュ。スレッド名を出力するコードを入れてるので別スレッドで実行されてることがはっきりしますね。
実行するとこんな感じ。


$ echo happy > test.txt
$ clj watch-file-status.clj test.txt
watching... (seq-test.txt)
main thread: main
watching-loop thread: pool-2-thread-1
;; $ echo chrismas! >> test.txt
modified: seq-test.txt
datetime: 2011-12-25 11:29:28
;; Ctrl-C
$ cat test.txt 
happy
chrismas!


これでfill-queueの使用例を書くことができました。前々から気になってたのをアウトプットできてスッキリ。
もうクリスマスがどうとか言ってる時期じゃないよ! 大掃除、棚卸しが大事だよ! リア充が爆発したら掃除が大変だよ!

ではみなさん良い年末を。俺は年賀状を書きます。

2011-12-10

Overtone: Clojureで音楽を書こう


この記事はClojure Advent Calendar 2011の10日目の記事として書いています。

12月1日からクリスマス25日まで毎日1ネタ、Clojureの記事を書くというこの企画。
せっかくなので前々から興味のあったOvertoneというライブラリの紹介をしてみようと思います。
Home // Overtone
overtone/overtone - GitHub

Overtoneは"Programmable Music"を目標とするシンセサイザーです。普通のシンセサイザーの操作とは違い、Clojureコードで音を作り音楽を書くことができます。
プログラマブル・シンセサイザーとしてはSuperColliderというのが前からあるそうで、それをClojureでラップした作りのようですね。
この記事では導入の仕方と簡単な音の作り方、最後にとあるクリスマスソングをOvertoneで書いてみたいと思います。

2011-11-28

Clojureコードを性能測定する3つの方法


ClojureハッカソンであるTokyo.clj#15に行って来ました。

そこで前々から気になっていたClojureでの性能測定(プロファイリング)の方法について試してみたのでまとめます。

性能測定といってもいろいろな切り口があるので、今回は「実行速度」と「メモリ使用量」に限定して調べて見ました。
その結果、次の3つが手軽でいいかなーと思ったので説明します。

  1. clojure.core/time
  2. clojure.contrib.profile
  3. VisualVM


1,2はClojure用の性能測定ツール、3のVisualVMはJava用のJVMモニタリングツールです。それぞれ一長一短があるので、まずは使い方から説明していきます。


【clojure.core/time】
S式の実行速度を表示する関数です。
『プログラミングClojure』でもよく使われているから知っている人は多いでしょう。
測定用のサンプルとして竹内関数を実装して計測して見ました。

; たらい回し関数(竹内関数)
(defn tarai [x y z]
  (if (<= x y)
    y
    (tarai
     (tarai (dec x) y z)
     (tarai (dec y) z x)
     (tarai (dec z) x y))))

user> (time (tarai 19 16 11))
"Elapsed time: 13.304 msecs"
19

REPL上でいちばん手軽に性能測定ができる方法ですね。



【clojure.contrib.profile】
拡張ライブラリにあるプロファイラ用ライブラリです。
timeだと全体の実行時間しか分からなかったんですが、これを使うと測定したい部分をもっと細かく指定できます。
まずコードの中で測定したい部分をprofでキーワードとともに包んでやります。
そのあとtimeと同じようにprofileから呼び出してやると、指定部分ごとに実行時間の平均/最短/最大と実行回数をまとめた表を見せてくれます。

(defn prof-tarai [x y z]
  (if (<= x y)
    y
    (prof-tarai
     (prof :arg-x (prof-tarai (dec x) y z))
     (prof :arg-y (prof-tarai (dec y) z x))
     (prof :arg-z (prof-tarai (dec z) x y)))))

user> (profile (prof-tarai 10 5 2))
 Name      mean       min       max     count       sum
arg-x    117410         0   2127000       100  11741000
arg-y     70040         0   2960000       100   7004000
arg-z     64230         0   1668000       100   6423000
nil

時間の単位はナノ秒です。

以下のように*enable-profiling*にfalseを指定してやると、コンパイル時にプロファイル用コードを除去してくれるそうです。

user> (binding [clojure.contrib.profile/*enable-profiling* false]
      (macroexpand '(prof :abc (+ 2 3))))
(do (+ 2 3))


以下ちょっと不満点
timeと違って実行結果は返してくれません。結果表をプリントするだけで常にnilを返します。
さらに計測で使ってるSystem.nanoTime()はlong型を返しますが、数分かかるような重い処理だと途中で桁あふれを起こして止まる場合があります(Integerでキャストしている部分が怪しい)。
ナノ秒で測っていることから考えて測定精度を優先して作られているのかなーと思いました。

Clojureメーリングリストを調べてみたら作者のStuart Sierraさんが「half-bakedなんで本気でやるならJVMプロファイラ使ってね」と言ってました。

ここらへん改良したのを作りたいなー。



【VisualVM】
上2つはClojure純正ツールでしたが、こっちはJava用のツールをClojureに応用してみたというものです。
JVMをモニタリングしてメソッド単位で実行速度・メモリ使用量などを計測してくれます。
そのほかにもいろいろ機能があるみたいなので細かいことを公式サイト参照すべし(日本語ドキュメントあり)。

VisualVM 入門 — Java.net

VisualVMを起動してClojureのREPLを立ち上げると、左側のツリービューにREPLの動いているVMが表示されます。それをダブルクリックするとこんな画面が開きます。



ここではCPU使用率とかヒープメモリ使用量のグラフを表示してくれます。このままでも十分有益なデータが得られますね。
プロファイル情報を得たいときは、[プロファイラ]のタブで画面を切り替えます。
[CPU]と[メモリー]のどっちかのボタンを押してから測定したいコードを実行すると、Javaのメソッド単位で処理速度やメモリ使用量を測ってくれます。


Clojureで定義した関数なんかはこんなふうにリストに出てきます。あくまでJava用ツールなのでClojure視点だと結果が読みにくいんですが、下にあるテキストボックスで絞込みが出来るので、それを上手く使うともうちょっと読みやすくなります。



【まとめ】
time、c.c.profileはREPL上で使うのに都合がいいです。ただし実行速度計測がメインで、メモリ使用量なんかは分かりません。チューニングが必要なときに「コード修正して計測」というループを素早く繰り返したいときには楽ですね。
VisualVMは高機能で網羅的ですが、開発中に何度も繰り返し使うというには重い。「ある程度完成しているアプリで何かボトルネックがある、その箇所にあたりを付ける」という場合に使うと良いと思います。

全体的にはClojureの性能測定ツールはまだ発展途上、という感想です。



【参考リンク】
Fatvat: JVisualVM and Clojure
Tokyo.clj#15 - Togetter
Tokyo.clj二次会ではClojure以外の言語の話題でもよく盛り上がって面白いです。

2011-10-25

Shibuya.lisp#7に参加してきました

Shibuya.lisp » Blog Archive » 2011/10/22 Shibuya.lisp テクニカルトーク #7 開催しました!!

最初に参加したのが#5なので、これで三回目です。


TechTalkでは最初の@m2ymさんの発表が気になりました。

括弧への異常な愛情 または私は如何にして心配するのを止めてCommon Lispを愛するようになったか


タイトルだけだと「信者乙!」と言いそうになりますが、中身は実際に開発する上での問題点をバランスのとれた視点で解説していて面白かったです。
私もLispに興味を持った最初のころはCommon Lispを使っていたんですが、ライブラリがどこにあってどうやって使うのかが分かりにくくて、結局フィボナッチ数列を計算させたぐらいで終わっていました。
これからCommon Lispを始めようというひとは目を通しておくと良いと思います。
また「マクロは万能ではない」という部分に関してはClojure-conjにあったこの発表を思い出しました。

(not= DSL macros) - Slideshare


そのほかの発表もLisp+ハードウェア、Lisp+セキュリティなど面白い組み合わせで興味深かったです。


会場に来た方たちともいろいろお話しできて面白かったです。私が話しかけた人は初参加というひとが多かったんですが、もっとがんがん話しかけちゃっていいと思いますよー。こういうイベントに集まるってことはまさにそういうコミュニケーションを求めているはずですから、話しかけられて嫌な人は居ないはずです。
あと初参加の人は次は発表する側にまわってみるのもいいですね。不安な人のために私がLTしたときのを貼っときましょう。

ClojureとEPUBで「普通のやつらの上を行く」方法

LTの良いところはどんなにスベっても5分で終わるところにあります(キリッ
またスベったらスベったで覚えてもらいやすくなります(キリッ

まぁ、自分が作っているものって欠点がよく見えてしまいがちですが、他人から見ると充分面白いかったりするので思い切って発表しちゃうのもいいですねー(今回LTしなかった自分への自戒も込めて)。



毎度のことながら運営者の方々、お疲れ様です。
次回、渋谷の街に迷わなかったら早めに行って準備を手伝いたいと思います(田舎者には難易度高いですが)。

2011-02-07

Clojureのシーケンス関数を使用例で説明してみる

《例示は理解の試金石》

Clojureではいろんなデータを「シーケンス」として扱います(Clojure - sequences)。だからシーケンスを操作する関数をいろいろ使うことになりますが、ちょっと複雑な操作をしようとすると基本的な関数だけでは足りなくて自分で処理を書くことになります。ところがあとで標準ライブラリをよく読んでみると「この関数使えばもっと簡単に処理を書けたんじゃん! 知らなかったばっかりに(ぐぬぬ」となることもあります。

拡張ライブラリのclojure.contrib.seqはちょっと便利なシーケンス関数をまとめたものですが、ドキュメントをちょっと読んだだけだと使いかたが分かりにくい。そこで各関数の使用例をまとめてみました。これでライブラリにある関数を再発明しないで済むはずだ!

gist: 780422 - GitHub
(ns intro-seq.core
(:use [clojure.contrib seq]))
;; Clojure初心者向けに覚えておくと便利なシーケンス操作関数の使用例を列挙する
;; 1.2でclojure.coreに移動し、廃止予定(DEPRECATED.)になってるのもある
;; 全部で18個(DEPRECATEDは8個)
;; 使用頻度高そう系(4個)
;; たまに使うかも系(3個)
;; パズルを解くときに使うかも系(3個)
;; シーケンスをグループ分け系(4個)
;; 自己参照する遅延シーケンスをつくる系(2個)
;; とりあえず知っとけばいいんじゃね?系(2個)
;; 使用頻度高そう系(4個)
;find-first
;シーケンスの中から条件に合う最初の要素を返す
(find-first #(> % 3) [1 2 3 4 5])
;=> 4
(find-first #(Character/isLowerCase %) "Clojure")
;\l
;includes?
;シーケンス内に要素あるか調べる
(includes? [1 2 3 4 5] 4)
;true
(includes? [1 2 3 4 5] 10)
;false
(includes? "clojure" \c)
;true
(includes? "clojure" \x)
;false
;flatten
;DEPRECATED. Prefer clojure.core version.
;階層構造のあるシーケンスを均す
(flatten '((1 2 3) (4 5 6)))
;=> (1 2 3 4 5 6)
; positions
; 条件が真になる要素の位置のシーケンスを返す
(positions #(> 4 %) '(1 2 3 4 5))
;(0 1 2)
(positions #(= \c %) "clojure is cool")
;(0 11)
;; たまに使うかも系(3個)
;indexed
; シーケンスの各要素をインデックス付きのベクタに変える
; clojure.core/map-indexedも便利
; C言語などのfor文のような感じで使える
(indexed '(:a :b :c))
;([0 :a] [1 :b] [2 :c])
(indexed "clojure")
;([0 \c] [1 \l] [2 \o] [3 \j] [4 \u] [5 \r] [6 \e])
;frequencies
;DEPRECATED. Prefer clojure.core version.
;シーケンスの要素の出現回数を数える
;要素をkey,出現回数をvalueにしたマップを返す
(frequencies [1 1 2 3 5 8])
;{8 1, 5 1, 3 1, 2 1, 1 2}
(frequencies "clojure is cool")
;{\space 2, \c 2, \e 1, \i 1, \j 1, \l 2, \o 3, \r 1, \s 1, \u 1}
;reductions
;DEPRECATED. Prefer clojure.core version.
; シーケンスの先頭から各要素までreduceした結果を返す
; rec-seqを使って実装されている
(reductions (fn [r x] (+ r x)) [1 2 3 4 5])
;(1 3 6 10 15)
(reductions (fn [r x] (* r x)) [1 2 3 4 5])
;(1 2 6 24 120)
;; パズルを解くときに使うかも系(3個)
;; パズルを解きたい時にはc.c.combinatoricsも見てみると楽になるかも
;rotations
;シーケンスの最初の要素を最後に持って行き、循環させたものを順番にシーケンス化
(rotations [1 2 3 4])
;((1 2 3 4) (2 3 4 1) (3 4 1 2) (4 1 2 3))
;rand-nth(rand-elt)
;DEPRECATED. Prefer clojure.core/rand-nth.
; ランダム選択
(rand-elt [1 2 3 4 5])
(rand-nth [1 2 3 4 5])
;4
(rand-nth [1 2 3 4 5])
;2
(rand-nth "Clojure")
;\e
(rand-nth "Clojure")
;\o
(rand-nth "Clojure")
;\o
(rand-nth "Clojure")
;\u
(rand-nth "Clojure")
;\o
;shuffle
;DEPRECATED. Prefer clojure.core version.
;ランダムシャッフル
(shuffle [1 2 3 4 5])
;(3 1 5 4 2)
(shuffle [1 2 3 4 5])
;(3 4 5 2 1)
(shuffle [1 2 3 4 5])
;(1 4 3 5 2)
(shuffle [1 2 3 4 5])
;(4 3 2 1 5)
;; シーケンスをグループ分け系(4個)
;group-by
;DEPRECATED. Prefer clojure.core version.
;シーケンスの要素に指定された関数を適用し、その結果が同じものをグループ化する
;関数の値をkey、vectorでグループ化したものをvalueにしたMapを返す
(group-by #(* 2 %) [1 2 3 4 5])
;{2 [1], 4 [2], 6 [3], 8 [4], 10 [5]}
(group-by odd? (range 10))
;{false [0 2 4 6 8], true [1 3 5 7 9]}
;partition-all
;DEPRECATED. Prefer clojure.core version.
; シーケンス要素を指定した個数ごとにまとめる
(partition-all 2 '(1 2 3 4 5 6 7 8))
;((1 2) (3 4) (5 6) (7 8))
(partition-all 3 '(1 2 3 4 5 6 7 8))
;((1 2 3) (4 5 6) (7 8))
(partition-all 1 2 '(1 2 3 4 5 6 7 8))
;((1) (3) (5) (7))
;partition-by
;DEPRECATED. Prefer clojure.core version.
; シーケンスを条件ごとに切り分ける
; シーケンス要素に関数を摘要し、同じ結果が返る連続する要素をグループ化
(partition-by #(> 3 %) '(1 2 3 4 5))
;((1 2) (3 4 5))
(partition-by odd? (range 10))
;((0) (1) (2) (3) (4) (5) (6) (7) (8) (9))
;separate
; シーケンスをfilterして取得したものと、それ以外を両方取得
; 奇数と偶数に分けてみる
(separate odd? (range 10))
;[(1 3 5 7 9) (0 2 4 6 8)]
;; 自己参照する遅延シーケンスをつくる系(2個)
;rec-seq
; 自己参照するシーケンスを作る
(take 10 (rec-seq self (lazy-cat (range 3) self)))
;(0 1 2 0 1 2 0 1 2 0)
;rec-cat
; 自己参照するシーケンスをconcatする
(take 10 (rec-cat fibs [1 1] (map + fibs (rest fibs))))
;(1 1 2 3 5 8 13 21 34)
;http://clj-me.blogspot.com/2009/01/recursive-seqs.html
;; とりあえず知っとけばいいんじゃね?系(2個)
;seq-on
;任意の型を(seq ~)でシーケンスに変換できるように定義できるマルチメソッド
;fill-queue
; スレッド間で通信できる?(よく分からない)
; ↓でfill-queueの使用例が見れる
;The Infolace Story: Simple webhooks with Clojure and Ring
;http://infolace.blogspot.com/2009/08/simple-webhooks-with-clojure-and-ring.html
view raw gistfile1.clj hosted with ❤ by GitHub



rec-seq、rec-catはイマイチ用途が分かってません。
最後のfill-queueだけ毛色が違いすぎます(入れる名前空間間違ってない?)。これは別に使用例を書く予定。

c.c.seqの関数はclojure.coreに移動予定のも多いです。coreに入っちゃうと他とごっちゃになって分かりにくくなると思うので今のうちに試してみては?


【参考リンク】
Clojure - cheatsheet
このチートシートを印刷しておくのも便利。

追記(2011-12-25)
fill-queueの使用例も書きました
fill-queueでお手軽非同期処理 : サルノオボエガキ

2011-01-10

Clojureでコマンドラインアプリを作るときはwith-command-lineが便利

追記(2013-12-11)
最近のClojureだったらtools.cljを使うほうが良いようです。

tools.cli: Clojureでコマンドライン引数を扱う - Qiita [キータ]




Clojureでコマンドラインアプリを作るときに便利なマクロを見つけたので、使い方をメモっておきます。

clojure.contrib.command-line/with-command-lineを使うと、コマンドラインでオプションを受け取る処理をスッキリ書けます。
何はなくともまず例示、ということで整数計算するスクリプトをwith-command-lineを使って書いてみたのがこれです。

gist: 770487 - GitHub
; int-calc.clj
(ns int-calc
(:use [clojure.contrib.command-line :only (with-command-line)]))
(with-command-line *command-line-args*
"clj int-calc.clj [-p|-mi|-ml] [-modulo n] nums.."
[[plus? p? "plus" true]
[minus? mi? "minus"]
[multiply? ml? "multiply"]
[modulo "mod n"]
num-strs]
(let [nums (map #(Integer/parseInt %) num-strs)
func (cond
plus? +
minus? -
multiply? *)
result (reduce func nums)]
(if (nil? modulo)
(println result)
(println (mod result (Integer/parseInt modulo))))))
view raw int-calc.clj hosted with ❤ by GitHub


option?のように?を付けると真偽値、?無しだと引数をその変数名で受け取ってくれます。オプションの省略記法も一緒に定義。オプション説明のあとに値を指定するとデフォルト値を設定してくれます。

これを実行するとこんな感じになります。自動でhelpオプションを表示してくれるのも気が利いてる。

$ clj int-calc.clj -p 3 5
8
$ clj int-calc.clj 3 5
8
$ clj int-calc.clj -p 3 5 -modulo 2
0
$ clj int-calc.clj -ml 3 5
15
$ clj int-calc.clj -ml 3 5 -modulo 2
1
$ clj int-calc.clj --help
clj int-calc.clj [-p|-mi|-ml] [-modulo n] nums..
Options
  --plus, -p        plus      [default true]
  --minus, --mi     minus                   
  --multiply, --ml  multiply                
  --modulo     mod n                   


こういう定型処理をサクっと分かりやすく書けるのは良いですね。

これに限らずclojure.contribライブラリには便利なものが揃ってて良いんですが、全体の量が多いしドキュメント読んだだけだと使い方がわからないことがちらほら。コーディングしてて「おー、なかなか気の利いたコードが書けたぜ!」と思ってたら、あとでライブラリにもっとエレガントな実装を見つけて凹むみたいなケースが何件かありました(text2epub-cljのcore.cljもこれで書き直した)。

日本語情報が少ないというのも良くない再発明をしちゃう原因のひとつかなーと思うので、こういう便利なのを見つけたらちょこちょこ書いていこうと思います。


【参考】
Building a Clojure app with a command-line interface? - Stack Overflow
Clojure関連質問サイト。英語情報で良ければかなり充実。
逆引きClojure
日本語情報だとこちら。こっちにも書きこんでますよ。