clojureでcat -n
ちょっと間が空いてしまったが、何をやっていたかと言うとこちらのページで
Haskellのお勉強をしてました。そして、CPANならぬhackageDB :: [Package]に
行ってみて、世の中には好きな人がいるもんだと、感心したりしてました。
そうそう、FreeBSDに、Rubyのgemならぬ、Haskellのcabalを入れようとSDの記事を
頼りに試していた所、cabal-installの所で躓いてしまいました。
曰く、cabalのVersionは、6.02より大きくて7.00以下が必要でっせ、と。
やけに、指定が細かいなあと思っていたら、cabal自身が入っていなかった。
しょうがないので、上記URLより、cabal自身をDL
runghc Setup.hs configure
runghc Setup.hs build
sudo runghc Setup.hs install
しましたよ。で、これが時間のかかる事しきり。大量のメモリーが要求されて、そ
れに耐えられない私のマシンは、スラッシングの嵐に陥ったのでした。
そろそろ、富豪マシンに乗り換えないとだめかな。
余りHaskellにのめり込むと、抜けられなくなるので、Haskellを理解しつつ
それを、他言語に翻訳してみます。ターゲットはclojure。お題は、cat -n
とします。
まず、clojureでファイル読み込みはどうするん? 調べてみたら、
user=> (slurp "c:/sakae/test.txt")
"This is Line 1.\r\n\"line 2.\"\r\n\r\nHere line 4, above line is null.\r\nThis is 'LAST-LINE'"
ファイル全体が、一つの文字列で帰ってきました。ちと扱いにくいなあ。分解は
どうやる? 正規表現が一応あるみたいです。(この場合は、行でsplitですね。)
user=> (re-seq #"[^\r\n]+" x)
("This is Line 1." "\"line 2.\"" "Here line 4, above line is null." "This is 'LAST-LINE'")
黒田さんや山本先生に怒られそうなので、なるべくなら避けたい所。
ああ、調べている途中で、joinに相当するのも見つけたので
user=> (apply str (interpose ":" ["A" "B" "C"]))
"A:B:C"
さて、clojureには、応援部隊が居て、contribにいろいろと公開されている。
それをつらつら眺めていたら、duck_streams.clj と言うのを見つけた。
;; This file defines "duck-typed" I/O utility functions for Clojure.
;; The 'reader' and 'writer' functions will open and return an
;; instance of java.io.BufferedReader and java.io.PrintWriter,
;; respectively, for a variety of argument types -- filenames as
;; strings, URLs, java.io.File's, etc. 'reader' even works on http
;; URLs.
;;
;; Note: this is not really "duck typing" as implemented in languages
;; like Ruby. A better name would have been "do-what-I-mean-streams"
;; or "just-give-me-a-stream", but ducks are funnier.
へぇ、Rubyなんてのに触れているよ。よし、こやつのお世話になろう。
それには、contribのjar化だな。
sakae@debian:/home/src/clojure-contrib$ ant
Buildfile: build.xml
init:
check_hasclojure:
[echo] WARNING: You have not defined a path to clojure.jar so I can't compile files.
[echo] This will cause some parts of clojure.contrib not to work (e.g., pretty print).
[echo] To enable compiling, run "ant -Dclojure.jar=<...path to clojure.jar..>"
[echo]
compile_clojure:
jar:
[jar] Building jar: /home/src/clojure-contrib/clojure-contrib.jar
[jar] Building jar: /home/src/clojure-contrib/clojure-contrib-slim.jar
BUILD SUCCESSFUL
Total time: 8 seconds
BUILD SUCCESSFULって出てるけど、8秒で終わる訳ないっしょ。失敗だね。
では、仰せに従って、
sakae@debian:/home/src/clojure-contrib$ ant -Dclojure.jar=/home/sakae/clj/clojure.jar
Buildfile: build.xml
init:
check_hasclojure:
compile_clojure:
[java] Compiling clojure.contrib.accumulators to /home/src/clojure-contrib/classes
[java] java.lang.IncompatibleClassChangeError (types.clj:14)
[java] at org.apache.tools.ant.taskdefs.ExecuteJava.execute(ExecuteJava.java:194)
あらら、古いclojureじゃ、だめみたい。svnで先端のclojureを取ってきて、やって
みよう。
sakae@debian:/home/src/clojure-contrib$ ant -Dclojure.jar=/home/src/clojure-dev/clojure.jar
Buildfile: build.xml
init:
check_hasclojure:
compile_clojure:
[java] Compiling clojure.contrib.accumulators to /home/src/clojure-contrib/classes
[java] Compiling clojure.contrib.command-line to /home/src/clojure-contrib/classes
[java] Compiling clojure.contrib.complex-numbers to /home/src/clojure-contrib/classes
[java] Compiling clojure.contrib.cond to /home/src/clojure-contrib/classes
:
jar:
[jar] Building jar: /home/src/clojure-contrib/clojure-contrib.jar
BUILD SUCCESSFUL
Total time: 1 minute 32 seconds
そうだよなあ、これぐらいの時間はかかるよね。DISKもガリガリ動いていたようだ
から、大丈夫だろう。
sakae@debian:/home/src/clojure-contrib$ ls -l *.jar
-rw-r--r-- 1 sakae sakae 262058 Apr 13 11:03 clojure-contrib-slim.jar
-rw-r--r-- 1 sakae sakae 2152373 Apr 13 11:13 clojure-contrib.jar
はて、どちらを使えばいいのだろう? ここは、大きい事はいい事だで行きます。
sakae@debian:/home/src/clojure-contrib$ java -jar /home/src/clojure-dev/clojure.jar clojure-contrib.jar
java.lang.Exception: Unable to resolve symbol: PK in this context (clojure-contrib.jar:0)
at clojure.lang.Compiler.analyze(Compiler.java:4337)
at clojure.lang.Compiler.analyze(Compiler.java:4283)
:
あらら、エラーですよ。先端を追うと、こういう事って、当たり前なんですかね。
ここは、contrib全体を使うんじゃなくて、必要最低限を使うようにして
みます。
duck-streams.clj は、jarファイルにまとめてcontribというnamespaceで使う
事を意図して書かれていますので、ちょいと改変しました。
(ns user ;;clojure.contrib.duck-streams
Haskellで言ったら、preludeみたいに、どこでも使える user-namespaceですね。
user=> (load-file "duck_streams.clj")
#'user/with-in-reader
user=> (def ls (read-lines (reader "test.txt")))
#'user/ls
("This is Line 1." "\"line 2.\"" "" "Here line 4, above line is null." "This is 'LAST-LINE'")
read-lines って、rubyにも有ったような気がしますが、気にしない気にしない。
まあ、これで、ファイルの内容が、メモリーに乗ってくれました。
後は、これと、行番号を合体して、表示するだけですね。
行番号は6桁で右寄せとしましょう。Javaのprintfがそのまま使えちゃたり
しますが、それじゃ、つまんない。Haskellのそれに習って、
user=> (defn rjust [n]
(str (reduce str "" (replicate (- 6 (count (str n))) "-")) n))
#'user/rjust
user=> (rjust 123)
"---123"
ちゃんと、関数にしました。(今は、debugの途中なので、spaceの変わりに - を
使ってます。)
次は、これらを組み合わせてみます。
user=> (doseq [ [n s] (map vector
(iterate inc 1) ls) ]
(println (str (rjust n) ": " s)))
-----1: This is Line 1.
-----2: "line 2."
-----3:
-----4: Here line 4, above line is null.
-----5: This is 'LAST-LINE'
nil
どうやら、主要部分は出来たっぽい。
遅延評価のおかげで、Haskellぽく書ける事が分かりました。
後は、ファイル名が決めうちになっている所を、コマンドラインから
取り込むようにすれば、出来上がり。
sakae@debian:/home/src/clojure-dev$ time ./catn.sh catn.sh
1: #^:shebang '[
2: exec java -cp clojure.jar clojure.lang.Script "$0" -- "$@"
3: ]
4:
5: ;;;; catn.sh target ; unix cat -n
6:
7: (load-file "duck_streams.clj")
8:
9: (def target (first *command-line-args*))
10:
11: (defn rjust [n]
12: (str (reduce str "" (replicate (- 6 (count (str n))) " ")) n))
13:
14: (doseq [ [n s]
15: (map vector
16: (iterate inc 1)
17: (read-lines (reader target)) )]
18: (println (str (rjust n) ": " s)))
19:
real 0m12.386s
user 0m0.032s
sys 0m0.048s
sakae@debian:/home/src/clojure-dev$
なんと、sleep が一切入っていないにもかかわらず、実行終了まで12秒もかかる
不思議な cat が、出来上がりましたよ。でも、一応名誉のために付け加えておく
と、2回目の実行時は、8秒に短縮されました。