clojure


昨日はコーヒー豆が切れたので、近くのコーヒー店に豆を買いに行った。
そしたら、そこには、JavaBeans大特価の看板がでかでかと出ていた。
JavaBeansって苦いんだよなぁーと、敬遠しましたよ。

どのぐらい苦いかと言うと、、、まだ、私がMac使いだった頃、鳴り物入りで
Java 1.01が出たんです。苦労して落としてきて、起動してみましたが、余りの
遅さに即、ごみ箱へ叩きこみましたよ。
それから、時代は経過し、女房用のWindows機が搬入された時、Ecripseを入れて
みましたが、こやつも起動したら、タバコを吸いに行くのが常体と成りました。
それで、余り使う事もなく、時間が経過し、、
どこからか、JavaBeansがいいらしいと聞きつけ、入れてみましたが、やっぱり
遅い。
それ以来、JRE環境を除いて、全てのJava開発環境を捨ててしまいました。
JREは、時々Netをうろうろしてると、サーバーサイドか何かのページにぶち
当たる事があるので、さすがに消すのは忍びないと温存してます。

そんな苦さを味わって来た訳ですが、昨日の看板で再びJava熱が。
今なら、Jrubyでしょうか。でも、ありきたり過ぎて面白くない。(Sunのあの人、
ごめんなさい、です。)

刺激を求める技術者に捧げるScala講座ぐらいが、丁度いいかなあ。
でも、有名になりすぎと言う事で今回はPASS。変わりに見つけてきたのが、

clojure

そそられる名前だ。それに、多分、Lisp発祥の地ボストン生まれっぽいのも
よさそうだ。(ほんとかどうかは、知らないけど)

Getting Started に則り、試運転してみる。

c:\app\clojure>java -version
java version "1.6.0_11"
Java(TM) SE Runtime Environment (build 1.6.0_11-b03)
Java HotSpot(TM) Client VM (build 11.0-b16, mixed mode, sharing)
c:\app\clojure>java -cp clojure.jar clojure.lang.Repl
Clojure
user=> (+ 1 2 )
3
user=> (. javax.swing.JOptionPane (showMessageDialog nil "Hello World"))
nil

いきなり、メッセージ箱が出てきたのには、びっくりしましただ。
javax.swing って、Tcl/Tkの Tkみたいなものだな、と、無知をさらけ出して
おこう。でも、すごいな、1行のS式で、GUIをやれるなんて。

user=> (cons 1 '(2 3 4))
(1 2 3 4)
user=> (car '(1 2 3))
java.lang.Exception: Unable to resolve symbol: car in this context (NO_SOURCE_FILE:4)
user=> (cdr '(1 2 3))
java.lang.Exception: Unable to resolve symbol: cdr in this context (NO_SOURCE_FILE:5)

えぇ? car も cdr も無いの? そんなん Lispじゃないぞ!!
まあ、括弧の無い lispである、ruby にも、car,cdr は、無いからなぁ。
それじゃ、ruby 風ならいいんかいな?

user=> (first '(1 2 3))
1
user=> (rest '(1 2 3))
(2 3)
user=> (last '(1 2 3))
3

ははは、ビンゴじゃん。ruby芸がこんな場面で役に立つとは。

所で、この REPL を終了するには、どうするの?
user=> (exit)
java.lang.Exception: Unable to resolve symbol: exit in this context (NO_SOURCE_FILE:13)
user=> (quit)
java.lang.Exception: Unable to resolve symbol: quit in this context (NO_SOURCE_FILE:14)
user=> (bye)
java.lang.Exception: Unable to resolve symbol: bye in this context (NO_SOURCE_FILE:15)
user=> (sayonara)
java.lang.Exception: Unable to resolve symbol: sayonara in this context (NO_SOURCE_FILE:16)
終了できんぞ。

user=> ^Z

c:\app\clojure>
ああ、やっと抜けたわい。

このページをつらつらと見ていくと、emacs とかとも連携出来るみたい。
後で、取ってきておこう。


後は、メインページから、面白そうな所を見つけ出して、適当に見ていく。

user=> (def factorial
  (fn [n]
    (loop [cnt n acc 1]
       (if (zero? cnt)
            acc
          (recur (dec cnt) (* acc cnt))))))
#'user/factorial
user=> (factorial 5)
120

Scheme 基準に考えると、define の変わりが def で、lambda の変わりが
fn と思われるな。また、上記の loopは、Schemeの場合の let と思われ。
cnt = n, acc = 1 としておいて、再帰してねか。
ちょいと癖がありそうな感じがするぞ。

まあ、ポール・グレアムの Arc よりは、想像が働くな。そう言えば、Arcの
消息はどうなったのでしょうか?
PGが、奥さんの尻に敷かれてしまったとか、赤ん坊のおむつ換えに忙しくて
パソコンに向かう余裕がないとか?

user=> (defn note [f]
  (let [mem (atom {})]
    (fn [& args]
      (if-let [e (find @mem args)]
        (val e)
        (let [ret (apply f args)]
          (swap! mem assoc args ret)
          ret)))))
#'user/note

user=> (defn fib [n]
  (if (<= n 1)
    n
    (+ (fib (dec n)) (fib (- n 2)))))
#'user/fib

user=> (time (fib 35))
"Elapsed time: 12755.383156 msecs"
9227465

user=> (def fib (note fib))
#'user/fib

user=> (time (fib 35))
"Elapsed time: 4.177067 msecs"
9227465

12秒かかっていた計算が、Noteを取ると(Webにある、memoizeは、使われていて
エラーになったので改名した)、4msですよ。SICPは偉大だぞ。

user=> (time (fib 35))
"Elapsed time: 0.247238 msecs"
9227465
ノートを見るだけなら、更に早い(先の4msのは、ノートを取りつつ、見てた
ので、ちと遅かったのだ)

おまけで、素のfibを petite でやってみると
> (time (fib 35))
(time (fib 35))
    no collections
    4516 ms elapsed cpu time
    4713 ms elapsed real time
    0 bytes allocated
9227465
でしたよ。ちょいと涙が出てきますね。
また、手続きを定義した時、#'user/fib と出てくるけど、これって aclっぽい。
ぶれない人、黒田さんも、納得されるかしらん?


ついでなので、もう少し遊んでみます。Hosted on the JVM のリンクに
あった、swing しましょの例。
さすが、プログラミング言語Cの最初の例ばかりでは、馬鹿にされると
思ったかどうか知りませんが、この本の2番目の例だったはず。
そう言えば、石田先生が最近、逝去されたとか、お世話になりました。合掌。

摂氏を華氏に変換するんだから、安直に下記のスクリプトを cf.clj とでも
しておくと、

(import '(javax.swing JFrame JLabel JTextField JButton)
        '(java.awt.event ActionListener)
        '(java.awt GridLayout))
(defn celsius []
  (let [frame (JFrame. "Celsius Converter")
        temp-text (JTextField.)
        celsius-label (JLabel. "Celsius")
        convert-button (JButton. "Convert")
        fahrenheit-label (JLabel. "Fahrenheit")]
    (.addActionListener convert-button
      (proxy [ActionListener] []
        (actionPerformed [evt]
          (let [c (Double/parseDouble (.getText temp-text))]
            (.setText fahrenheit-label
               (str (+ 32 (* 1.8 c)) " Fahrenheit"))))))
    (doto frame
      (.setLayout (GridLayout. 2 2 3 3))
      (.add temp-text)
      (.add celsius-label)
      (.add convert-button)
      (.add fahrenheit-label)
      (.setSize 300 80)
      (.setVisible true))))
(celsius)

java -cp clojure.jar clojure.lang.Script cf.clj

これで楽しめる。窓を閉じてもスクリプトが終了しないので、最後は ^C で
終了させましょう。