あんまりホリデイっぽくないので、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サーバー処理になにかフックさせるときに使ってる。途中の図がわかりやすい。
これでも使用例としてはいいんですけど、もうちょっと手元でいじれる例が欲しいのでファイルの最終更新日時を監視するスクリプトを書いて見ました。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;; 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の使用例を書くことができました。前々から気になってたのをアウトプットできてスッキリ。
もうクリスマスがどうとか言ってる時期じゃないよ! 大掃除、棚卸しが大事だよ! リア充が爆発したら掃除が大変だよ!
ではみなさん良い年末を。俺は年賀状を書きます。