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  
where possible options include:
  -g      Generate all debugging info
  :

一応、-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.