clojureを分解する(2)
クリック猿後遺症の指の痛みも治まってきたので、Suspendしていた Clojureの分解を
Resumeしようと思ったのだが、どこまでやったかの記憶がリークしてしまっていて、reboot
を余儀なくされている。全く、鶏頭には困ったものである。
リハビリを兼ねて、clojureの面白そうな所を、もう少し探ってみよう。
user=> (def foo (ref {:a "fred" :b "ethel" :c 42 :d 17 :e 6}))
#'user/foo
user=> @foo
{:e 6, :a "fred", :b "ethel", :c 42, :d 17}
Lispは、pointerだけで出来ているから(ポール・グレアム談)、面倒な事が無いと
思っていたら、とうとう悪夢が持ち込まれてしまったようだ。
user=> (assoc @foo :a "lucy")
{:e 6, :a "lucy", :b "ethel", :c 42, :d 17}
更新できたように思えるけど、、、
user=> @foo
{:e 6, :a "fred", :b "ethel", :c 42, :d 17}
更新されていない。
どうやったら更新出来るかと言うと
user=> (dosync (commute foo assoc :a "lucy"))
{:e 6, :a "lucy", :b "ethel", :c 42, :d 17}
user=> @foo
{:e 6, :a "lucy", :b "ethel", :c 42, :d 17}
安全性が高まる?
次は、エージェント機能だ。
user=> (def foo (agent {:a "fred" :b "ethel" :c 42 :d 17 :e 6}))
#'user/foo
user=> @foo
{:e 6, :a "fred", :b "ethel", :c 42, :d 17}
user=> (send foo assoc :a "lucy")
#<Agent clojure.lang.Agent@23e5d1>
user=> @foo
{:e 6, :a "lucy", :b "ethel", :c 42, :d 17}
user=> (await foo)
nil
user=> @foo
{:e 6, :a "lucy", :b "ethel", :c 42, :d 17}
次は、clojure からJavaを呼び出す離れ技
user=> (new java.util.Date)
#<Date Tue Mar 24 12:57:00 JST 2009>
さて、clojureの分解だが、この間作ったソース閲覧Webを使って、あちこちを飛び回った
結果、個々の部分は、だいぶ分かってきた。
たとえば、consだが、Lispのconsだからと思って、DEFINITIONSから cons を探すとだめで
ある。Lispのconsは、アリティーが2のはずであるが、Javaソース上のやつは、1引数な
のだ。(良く、目をこらして見ると、clojure/lang/RT.java ... cons(Object x, Object coll)
だけが、まともな cons っぽい?)
正解は、Cons の方なのだ。ここからがJavaのいやらしい所で(単に私がJavaと言うか、
オブジェクト指向を知らないだけですが)、lang/Cons.java で、Consと言うクラスが
定義されてて、実体(親の方)は、別の所に書いてある。追ってみると、Aseq.javaにあった。
98 public ISeq cons(Object o){
99 return new Cons(o, this);
100 }
もっと先を辿れですって。きりが無いので、ご勘弁を。
嗚呼、もう一つJavaのやらしい事が有った。
実体の無い、定義だけしてある(interfaseって言うんでしたっけ?)ファイルは
*.java じゃなくて、*.h みたいに、それとすぐに分かる名前になりませんかね。
JavaでLambdaもいいけど、是非実現してくらはい! >ジェームス・ゴスリン殿
ファイルを開いてみて、腹が立つ事しきりですんで!
それより、「木を見て森を見ず」という方が、気になります。
たとえば、clojure.jarを分解すると、core.cljがそのまま入っていたりします。
cljureが動き出した時、core.cljがどのように読み込まれるか等の、マクロな
動きを知っておきたい。
その為には、デバックオプションを付けて、コンパイルしておいて、その
出来たオブジェクトを、debuggerの上で走らせればいいのだが。。。
Javaで、そんな事は出来るのだろうか?
sakae@debian:~/clj$ javac
Usage: javac
一応、-g は、あるみたいだな。でも、どうやって ant に組み込むの?
どうやって、gdb みたいなのを起動するの? Javaのdebuggerだから jdb ?
jdbって、jarも扱えるの? 後で調べておこう。乗りかけた舟なんだから!
(自慢じゃないけど、Java関連本は処分しちゃって、一冊もありません。)
正攻法もいいけど、他の方法って無いものだろうか?
考える
考える
考える
考える
考える
考える
考える
考える
考える
考える
考える
考える
考える
考える
考える
考える
名案が浮かばないので、散歩に行ってくる。途中で出会った野良猫達に相談してみるよ。
考える
考える
考える
考える
考える
以下、
考える x 100
(注
無闇に行数をかぜぐ、行の秤売りは、Javaやtidiperlに習いました。
if xxxx
{
.....
}
else
{
.....
}
より
if xxx {
.....
} else {
.....
}
の、方が、私は、好きです。 /注)
思いついたのは
火事場泥棒
騒ぎに乗じて、泥棒しちゃえ です。
clojureもきっちりと
try {
.....
} catch {
.....
}
で、防御を固めているので、難しいかも知れませんが 。。
編み出した方法は、
Windowsから、リモートでDebian機に入り、cljを起動。
その上で、GUIのアプリ(初回にやった摂氏華氏変換)を起動。
Windows上にXのアプリを表示しようとして、右往左往するに違いない。
防御が甘いと、例外が起きて、スタックトレースしてくれると思われ。
sakae@debian:~/clj$ java -cp clojure.jar clojure.lang.Script cf.clj
Exception in thread "main" java.awt.HeadlessException:
No X11 DISPLAY variable was set, but this program performed an operation which requires it. (cf.clj:0)
at clojure.lang.Compiler.eval(Compiler.java:4153)
at clojure.lang.Compiler.load(Compiler.java:4470)
at clojure.lang.Compiler.loadFile(Compiler.java:4437)
at clojure.lang.Script.main(Script.java:65)
Caused by: java.awt.HeadlessException:
No X11 DISPLAY variable was set, but this program performed an operation which requires it.
at java.awt.GraphicsEnvironment.checkHeadless(GraphicsEnvironment.java:159)
at java.awt.Window.(Window.java:431)
at java.awt.Frame.(Frame.java:403)
at javax.swing.JFrame.(JFrame.java:207)
at clojure.core$celsius__4.invoke(cf.clj:4)
at clojure.core$eval__11.invoke(cf.clj:24)
at clojure.lang.Compiler.eval(Compiler.java:4142)
... 3 more
sakae@debian:~/clj$ cat -n cf.clj
:
4 (defn celsius []
5 (let [frame (JFrame. "Celsius Converter")
:
24 (celsius)
やったね。「人の不幸は蜜の味」と言いますが、部分的にも実行の流れが
見えたぞ。でも、この件は作者へ報告しておくべきだろうか?
Exception節のトレースは、ソース通りの流れなので、よーく分かります。
問題は、Caused節の方だな。... 3 more で、隠れている部分があるのは残念
だけど、clojure.lang.Compiler.eval(Compiler.java:4142)の該当する
ソースを見ると、return fn.invoke(); と、なっている。
clojureのlisp関数を起動する部分だ。
clojure.coreに定義されている、evalを使って、cf.clj内の(celsius)を
呼び出す、それを実行するには、cf.clj内の手続き定義を行う必要がある。
呼び出しの連鎖が行われているのだ。
ここまで分かっても、まだ不満は募るばかり。
自分は一体、何を調べたいん??
1. consがどう構成されるか?
2. evalはどう作られている?
3. Java上にclojureがどのように、インプリメントされているか?
1は、既に見たよな。2はどうだ。clojure.core$eval__11で実現されてる
よな。その元になったソースは? 勿論、core.clj だよな。こいつは、
clojureの起動時にすでに存在してると。
3.は、どうだ。本当は、これが一番知りたい所。
もう少し、高所に上って俯瞰する必要がありそうだ。
と、言う事で、多分、 to be continue.