Hadoopの簡単なサンプルをClojureで書いてみました。leiningenでビルドするとらくちんでした!
leiningen 1.1.0対応しました(2010/03/10)
サンプルコードはオライリーのHadoop本、例2-6。Java版はこちら。
(defproject clj-max-temperature "0.1.0-SNAPSHOT"
:description "Sample of Hadoop on Clojure"
:dependencies [[org.clojure/clojure "1.1.0"]
[org.clojure/clojure-contrib "1.1.0"]
[org.apache.mahout.hadoop/hadoop-core "0.20.1"]
;; ここから先は全部必要か分からない
[commons-cli/commons-cli "1.2"]
[commons-codec/commons-codec "1.3"]
[commons-el/commons-el "1.0"]
[commons-httpclient/commons-httpclient "3.0.1"]
[commons-logging/commons-logging "1.0.4"]
[commons-logging/commons-logging-api "1.0.4"]
[commons-net/commons-net "1.4.1"]]
:dev-dependencies [[leiningen/lein-swank "1.1.0"]]
:main clj_max_temperature)
(ns clj_max_temperature
(:gen-class)
(:import (org.apache.hadoop.fs Path)
(org.apache.hadoop.io IntWritable LongWritable Text)
(org.apache.hadoop.mapreduce Job Mapper Mapper$Context Reducer Reducer$Context)
(org.apache.hadoop.mapreduce.lib.input FileInputFormat)
(org.apache.hadoop.mapreduce.lib.output FileOutputFormat)))
(gen-class
:name clj_max_temperature.mapper
:extends org.apache.hadoop.mapreduce.Mapper
:prefix "mapper-")
(defn mapper-map [this key value #^Mapper$Context context]
(let [line (str value)
year (.substring line 15 19)
quality (.substring line 92 93)
air-temperature (Integer/valueOf
(.substring line
(if (= (.charAt line 87) \+) 88 87)
92))]
(if (and (not (= air-temperature 9999))
(.matches quality "[01459]"))
(.write context (Text. year) (IntWritable. air-temperature)))))
(gen-class
:name clj_max_temperature.reducer
:extends org.apache.hadoop.mapreduce.Reducer
:prefix "reducer-")
(defn reducer-reduce [this key #^Iterable values #^Reducer$Context context]
(.write context key (IntWritable. (reduce max (map #(.get %) values)))))
(defn -main [& args]
(when (not (= (count args) 2))
(.println System/err "args error!")
(System/exit -1))
(let [job (Job.)]
(FileInputFormat/addInputPath job (Path. (nth args 0)))
(FileOutputFormat/setOutputPath job (Path. (nth args 1)))
(doto job
;; リテラルで現在のクラスの取り方が分からない。。。
(.setJarByClass (Class/forName "clj_max_temperature"))
(.setMapperClass (Class/forName "clj_max_temperature.mapper"))
(.setReducerClass (Class/forName "clj_max_temperature.reducer"))
(.setOutputKeyClass Text)
(.setOutputValueClass IntWritable))
(System/exit (if (.waitForCompletion job true) 0 1))))
Reducerの中でmap/reduce使ってるのがいけてるでしょ!
プロジェクトはこちら
$ git clone git://github.com/making/clj-max-temperature.git $ cd clj-max-temperature
$ lein compile # uberjarだけでcompileも行われるが、コンパイルエラーが発生してもjar作成に進むので開発時は別に実行した方が良い $ lein uberjar $ hadoop jar clj-max-temperature-standalone.jar input/sample.txt output
でおk。(スタンドアローンモードでのみ確認)
スタンドアローンなら、Hadoopをインストールしなくても
$ java -Xmx1000m -jar clj-max-temperature-standalone.jar input/sample.txt output
だけでもおk。簡単な動作確認に良さそう。
もっとMapReduceを使いやすくなるマクロをつくりたいね。
こんなのももうあるけど。
Created at : 2010-02-22 03:21:30
Updated at : 2010-03-20 04:54:16
Category : Programming::Lisp::Clojure::Leiningen
Clojureはマクロ定義内のシンボルを名前空間で修飾する。シンボル束縛を回避するためであり、バグを防いでくれるが、次のような書き方をしたいときはNG。
(defmacro foo [x]
`(defn ~x [x]
x))
user> (macroexpand-1 '(foo aaa)) (clojure.core/defn aaa [user/x] user/x) user> (foo aaa) Can't use qualified name as parameter: user/x [Thrown class java.lang.Exception]
シンボル束縛を強制的に行うためには~'をつける。
(defmacro foo [x]
`(defn ~x [~'x]
~'x))
user> (macroexpand-1 '(foo aaa)) (clojure.core/defn aaa [x] x) user> (foo aaa) #'user/aaa user> (aaa 100) 100
使うときは要注意。
尤も、このケースでは(gensym)を使うべきなのだが。。。
→ (追記:2010/03/03)Clojureはマクロ内で使うシンボルの末尾に「#」をつければ勝手にgensymしてくれます。
user> (defmacro foo [x] `(defn ~x [x#] x#)) #'user/foo user> (macroexpand-1 '(foo aaa)) (clojure.core/defn aaa [x__4956__auto__] x__4956__auto__)
Created at : 2010-02-26 03:22:45
Updated at : 2010-03-20 03:09:05
Category : Programming::Lisp::Clojure
これまでさんざんClojureのビルドツールとしてLeiningenを推してきましたが。Leiningenの気に入らない点として
を感じていました。
mavenなら↑のようなことはないので、mavenでClojureがコンパイルできてrepl起動して、swank-server立ち上げられるといいなと思っていたら、とっくにありますね、clojure-maven-plugin。
このプラグインを使ってすぐにMavenでClojureを使えるようにblankプロジェクトを作ってGithubにコミットしました。
maven-clojure-blank
$ git clone git://github.com/making/maven-clojure-blank.git
.gitディレクトリを削除して、project名(ディレクトリ名、pom.xmlの設定)を変えて使ってください
mvn compileでClojureコードもJavaコードもコンパイルできます。ソースフォルダに
src/main/clojure/am/ik/blank/hello.clj src/main/java/am/ik/blank/Hello.java
があったとき、
$ mvn compile $ find target/classes target/classes target/classes/am target/classes/am/ik target/classes/am/ik/blank target/classes/am/ik/blank/hello$foo__3.class target/classes/am/ik/blank/hello$loading__6309__auto____1.class target/classes/am/ik/blank/Hello.class target/classes/am/ik/blank/hello__init.class
と両方classファイルできてますね。OK。
Clojureのコンパイルだけしたい場合はmvn clojure:compile
src/test/clojureにテストコード書いておけばmvn testでjavaのテストと一緒にclojureのテストも行われます。
Clojureのテストだけしたい場合はmvn clojure:test
replもできます。特徴はクラスパスでしょう。
$ mvn clojure:repl Clojure 1.1.0 user=> (use 'clojure.contrib.classpath) nil user=> (doseq [cp (classpath)] (println cp)) #<File /Users/maki/work/maven-clojure-blank/target/classes/../generated-sources> #<File /Users/maki/work/maven-clojure-blank/src/main/clojure> #<File /Users/maki/work/maven-clojure-blank/src/test/java> #<File /Users/maki/work/maven-clojure-blank/src/test/clojure> #<File /Users/maki/work/maven-clojure-blank/target/classes> #<File > #<File /Users/maki/work/maven-clojure-blank/target/classes> #<File /Users/maki/.m2/repository/org/clojure/clojure/1.1.0/clojure-1.1.0.jar> #<File /Users/maki/.m2/repository/org/clojure/clojure-contrib/1.1.0/clojure-contrib-1.1.0.jar> #<File /Users/maki/.m2/repository/jline/jline/0.9.94/jline-0.9.94.jar> #<File /Users/maki/.m2/repository/junit/junit/3.8.1/junit-3.8.1.jar> #<File /Users/maki/.m2/repository/swank-clojure/swank-clojure/1.1.0/swank-clojure-1.1.0.jar> nil
clojureのソースディレクトリ、ビルド出力ディレクトリ、pom.xmlで指定した依存ライブラリが含まれています。これでJavaをコンパイルしてもすぐにreplで使えますね。
Lisp開発と言えばやっぱりSlimeを使いたいですよね。mavenでswank-serverを立ち上げることでrepl同様のクラスパス込の状態で使えます。mvn clojure:swankでswank立ち上げたあとemacsでM-x slime-connectでOK。
以下のようにconfigurationのscriptに実行したいclojureスクリプトのパスを書いておけばmvn clojure:runでロードされます。もちろんクラスパスは同上。
<plugin>
<groupId>com.theoryinpractise</groupId>
<artifactId>clojure-maven-plugin</artifactId>
<version>1.3.1</version>
<configuration>
<script>src/main/script/run.clj</script>
</configuration>
...
</plugin>
これだけ書くとleiningenいらないじゃんって思いそうですが、
mavenは立ち上がりが遅いのでちょっとイラッとするかもです。(Leinigenも結構遅いですがw)
Javaでライブラリを開発してClojureで呼び出すっていう使い方をしたい場合はこの方式が良さそうです。
ClojureのみのプロジェクトであればLeiningenで十分かなと思います。
Mavenについては
が詳しいです。
実はここからPDFでまるごとDLできますw
Created at : 2010-03-14 01:50:58
Updated at : 2010-03-14 02:48:18
Category : Programming::Lisp::Clojure