seesaw

Webをうろいろしてたら、面白いページに行き当たった。 計算尺推進委員会

かの昔、アポロ宇宙船にも搭載されていたという古きよき時代の計算ツール。日本が誇る いにしえの計算ツール、算盤は宇宙船に載っていたという話は聞かないから、計算尺は 近代計算の礎なんだろうな。ネイピアさんに感謝しましょう。

オイラーと計算尺の関わりは、、、そう、その手の学校でした。いわゆる工学系ね。計算命って 所です。計算尺の検定2級だったかを取らされたなあ。

計算尺ってネイピアさんの発見した対数を応用した道具。掛け算は対数の足し算だよ、割り算は、 対数の引き算だよってのが基礎になってる。

基本操作は掛け算(1)あたりを 見ればいいけど、数を浮動小数点として見た場合、計算尺で計算出来るのは、仮数部のせいぜい3桁が アナログ的に求まるだけ。指数部(桁合わせ)は、人間様が頭の中でやってねって、協調動作を する事になる。

失敗学の勧めで有名な畑中先生も言ってたけど、工学ではざっくりどのくらいってのが重要で、 桁を間違える事は、非常に大きな許せない見過ごせない失敗との事。

そりゃそうだわな。65cmの長さの俵を敷き詰めて、直径4.55mの土俵を作るとしたら、俵は何個 必要? なんて問題が出された時こそ、計算尺の出番です。(暗算でやるのがスマートでしょうけど)

こういう問題は、答えが小数点以下3桁も求めたって意味無い。ざっくり、俵が1桁で済むのか 2桁(10個台)になるのかが重要。

たまには触ってみたいな。計算尺。現代コンピュータ上で再現出来ないかしらん。 JavaScript上の計算尺

読書メーター 7冊 / 2247頁 / 10076円

シーソーその前に

この間からclojureに嵌まっている。clojureの元気の元はJava。ならば、Javaでお絵かきが 出来れば、仮想計算尺だって、きっと作れるに違いない。なんたって、Javascript*でも*実現 出来ているんだから。

clojureからJavaのスイングだかを使うやつが有ったはず。シーソーとか言ったな。昔ちょっと やった事がある。まあ、その前にJavaでお絵かきとかはどうするの? ちょいと調べておく。

WikiのSwingの項の説明によれば、Javaのお絵かき第1弾はAWTとか言うやつだったらしい。 どこでもJavaを標榜してJavaが生まれたけど、Javaが載るOSによって、GUIの見え方が違っていた。 Windows上のJAvaで開発したGUIをLinuxに持ってくると、細かい調整が必要になるとか、 いろいろ不評だったんでしょうな。まるで、EXCELL対オープンオフィスみたいにね。

それじゃ、天下取れないじゃんと発奮して開発されたのが第2弾のSwing。これで天下を 取れたかと思ったけど、どこかの後塵を拝した。そこでJavaFXが開発されたって歴史に なるのかな。だから、Swingを知っておけば、まあ何とかなるか。決してFXなんて言う怪しい物に 手を出してはいけません。身の破滅を招きます。

SwingでJavaに強くなる

Swingを使ってみよう

Swing入門

正直、面倒くせぇー!! こんなのやってられるか。そこでだ、

seesaw

seesawの出番ですよ。Javaとclojureの間をぎっこん・ばったんしてねって 思いで、作者さんは名前を付けたんでしょうかね。残念ながら、2年ほど前に、開発が 停止しちゃってるみたいですけど。後は元気のある奴に任せるとな。

これは必然なのでしょうね。元になるSwingも止まったままですから。枯れたやつも たまにはいいか。どのぐらいいいかと言うと、

(ns hello-seesaw.core
  (:use seesaw.core))

(defn -main [& args]
  (invoke-later
    (-> (frame :title "Hello",
           :content "Hello, Seesaw",
           :on-close :exit)
     pack!
     show!)))

これがGUI版の例のやつです。seesawは、1.4.4版が手間隙かからずにlein depsで取り寄せ られます。とまあ、ここまではいいのですが、上記からGUIに発展させていくには至難の技。 どこかにサンプルは無いか? 丁度目立つ所にexampleが有ったのでその中のREADME.mdを 見たら、詳しくはtest/seesaw/test/examplesを参照との事。

で、その中のREADMEを見ると、

  $ lein examples

で、GUIのサンプルの発射台が現れるとな。はて?

examples

leinのアーギュメントにexamplesなんてのが有ったかしら? project.cljを覗いてみたら 納得する答えが書いてあった。

  ; To run the examples:
  ;
  ;   $ lein examples
  ;
  :aliases { "examples" ["run" "-m" "seesaw.test.examples.launcher"] }

エイリアスなんてshellまがいの機能がleinには備わっているのね。知りませんでしたよ。 ついでに、-m ってオプションを調べてみると、どうやら、(-main) を起動する特技が あるらしい。

発射台

サンプルの中にサンプルとして発射台が用意されてるって、再帰してんな。これぞLisp。

早速、Windowsから起動しようとしたら

C:\Users\sakae\Downloads\seesaw-develop>lein examples
Java compiler not found; Be sure to use java from a JDK
rather than a JRE by modifying PATH or setting JAVA_CMD.

なぬー! JDKが必要難解。こりゃ困った。WindowsまでにJDKなんて入れる積もりは無いんよ。 何ったって、おいらはJavaのライトユーザーですから。しゃーない、万次郎Linuxの出番だな。

起動してみると、色々なサンプルが列挙されたWindowがポップアップされてきました。どれかを 選んでおいて、最下段にある発射ボタンを押すと、サンプルのGUIが動きます。 これって、一頃のTcl/Tkにあった陳列台じゃないですか。こんな事も出来末世って自慢の種だな。

これ、どう動いているの? 発射台の設計図をちらちらと眺めましたよ。そしたら、どうやら 核はこちらの様。

(defn launch-example [s]
  (let [example-ns (str "seesaw.test.examples." s)
        _          (require (symbol example-ns))
        run-fn     (resolve (symbol example-ns "run"))]
(println run-fn)  ;; For monitor
    (if run-fn
      (run-fn :dispose)
      (alert (str "Couldn't find function " (symbol example-ns "run"))))))

GUIと言いつつ、核がこういう所に潜んでいるとは面白いな。GUIの華やかさに目移りしては いけません。

user> (ns seesaw.test.examples.launcher)
;;=> nil
seesaw.test.examples.launcher> (launch-example 'clock)
Reflection warning, seesaw/test/examples/clock.clj:79 - reference to field getSize can't be resolved.
#'seesaw.test.examples.clock/run
;;=> #<JFrame$Tag$a79ba523 seesaw.core.proxy$javax.swing.JFrame$Tag$a79ba523[frame0,0,28,400x400,layout=java.awt.BorderLayout,title=Seesaw Canvas Clock,resizable,normal,defaultCloseOperation=DISPOSE_ON_CLOSE,rootPane=javax.swing.JRootPane[,5,25,390x370,layout=javax.swing.JRootPane$RootLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=16777673,maximumSize=,minimumSize=,preferredSize=],rootPaneCheckingEnabled=true]>

これで、時計が出てきましたよ。指定したサンプルをrequireして、その中のrunという関数を 起動してるとな。でも、runなんてのは、目を皿のようにして眺めても見つからんぞ。

この秘密はいかに?

展開するぞ

思い立つのは、マクロでrunとかを生成してるぐらいでしょうかね。そう言えば、-mで起動 される(はず)の(-main)も発射台のサンプルには無いし。。。

各サンプルの一番最後に鎮座してる、defexampleっていう関数定義っぽいのが怪しいな。 こやつを調べてみると、example.cljにマクロが出てた。

マクロを一見して展開形を描ききれなかったので(すんません、修行が足りなくて)、ciderに 展開して貰おう。展開のお題は、RPN計算機。

(defexample []
  (->
    (layout)
    behave
    (apply-stylesheet style) ; <- NOTE use of experimental/alpha function
    ))

最後にカーソルを合わせて、C-c RET すると、

(do
  (defn run [on-close__8932__auto__ & args__8933__auto__]
    (let [[] args__8933__auto__
          f__8934__auto__ (invoke-now
                            (-> (layout)
                             behave
                             (apply-stylesheet style)))]
      (config! f__8934__auto__ :on-close on-close__8932__auto__)
      (when (= (java.awt.Dimension.) (.getSize f__8934__auto__))
        (pack! f__8934__auto__))
      (show! f__8934__auto__)))
  (defn -main [& args__8933__auto__]
    (apply run :exit args__8933__auto__)))

おおお、出てきたぞ。runと -mainが。-mainは、:exitってアーギュメントを付けてrunを 呼び出してる。こういう形は前回、おいらも気が付いていたな。Lisp脳が成長中だな。

runの中身は、やっぱりGUI表示の基本形に還元されてるな。決まりきった事は、マクロの 中に隠して、本筋だけをプログラミングしてねって事ですな。こういうのに出会うと、 やれオブジェクト嗜好で、継承だとかほざくのが馬鹿らしくなるよ。

ついでに、発射台を発射出来るか、検証しとく。

user> (ns seesaw.test.examples.launcher)
;;=> nil
seesaw.test.examples.launcher> (-main)
;;=> #<JFrame$Tag$a79ba523 seesaw.core.proxy$javax.swing.JFrame$Tag$a79ba523[frame0,0,28,200x500,invalid,layout=java.awt.BorderLayout,title=Seesaw Example Launcher,resizable,normal,defaultCloseOperation=EXIT_ON_CLOSE,rootPane=javax.swing.JRootPane[,5,25,190x470,invalid,layout=javax.swing.JRootPane$RootLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=16777673,maximumSize=,minimumSize=,preferredSize=],rootPaneCheckingEnabled=true]>

告白

と、ここまですいすいと進んできたように書いちゃったけど、最初は戸惑いがありましたよ。 何に心を惹かれてしまったかと言うと、leinで言うtestエリア内にexamplesが置いてあった事。

きっとテストの一環として例が書かれているんだろうって、想像しちゃったのね。 テストなんて大嫌いなんで、避けてたのさ。でも、いよいよテストに向き合わなくちゃならないかと、 気が滅入ってしまったのは事実。

そこでだ、テストを知るって事で、シーソーはどこ製のテストを使ってるか調べてみましたよ。 いわゆるテスト対策ですな。敵を知れば、きっと突破も容易だろると思ってね。

project.cljを見ると、[com.stuartsierra/lazytest "1.1.2"]なんて業者名が書いてあったよ。 lazytestが、それらしい。 斜め読みした所、Rspec風に書けるとな。Rubyの時にしっかりやっとくんだったわい。

(describe + "with integers"
  (it "computes the sum of 1 and 2"
    (= 3 (+ 1 2)))
  (it "computes the sum of 3 and 4"
    (= 7 (+ 3 4))))

こんな風に自然に書けるとな。これを使った専用ドライバーが用意されてた。

[sakae@manjaro seesaw-develop]$ cat lazytest.sh
#!/bin/sh

JAVA_OPTS="-XX:MaxPermSize=512m -Xmx512m"
java $JAVA_OPTS -cp `lein classpath` lazytest.main test

走らせてみる。

[sakae@manjaro seesaw-develop]$ sh lazytest.sh
   :
Namespaces
    seesaw.test.event
        #'seesaw.event/unappend-listener
            can remove a listener
        #'seesaw.event/listen-to-property
            registers a property change listener
    :
    seesaw.test.icon
        #'seesaw.icon/icon
            returns nil given nil
            returns its input given an Icon
            returns an icon given an image
            returns an icon given a URL
            returns an icon given a path to an icon on the classpath
            returns an icon given a File
            returns an icon given a i18n keyword

Ran 990 test cases.
0 failures.

でも、どうも可笑しいって事で、ソースを再度読んだら、上記の仕組みに辿りついた次第。 思い込みは恐いな。

Windowsでもサンプル実行

しようと思ったんだ。それには、発射台にセットされてる各種ロケットをバラして持って来るのが 手っ取り早い。

[sakae@manjaro gr]$ diff -u clock.clj clk1.clj
--- clock.clj   2014-09-23 06:07:43.000000000 +0900
+++ clk1.clj    2014-09-23 06:13:01.000000000 +0900
@@ -8,11 +8,24 @@
 ;   the terms of this license.
 ;   You must not remove this notice, or any other, from this software.

-(ns seesaw.test.examples.clock
+(ns clk1
   (:use seesaw.core
         seesaw.graphics
-        seesaw.color
-        seesaw.test.examples.example))
+        seesaw.color))
+
+(defmacro defexample
+  [arg-vec & body]
+  `(do
+     (defn ~'run [on-close# & args#]
+       (let [~arg-vec args#
+             f# (invoke-now ~@body)]
+         (config! f# :on-close on-close#)
+         (when (= (java.awt.Dimension.) (.getSize f#))
+           (pack! f#))
+         (show! f#)))
+
+     (defn ~'-main [& args#]
+       (apply ~'run :exit args#))))

 ; A very rudimentary example of (canvas) that draws an analog clock

こんな風に、マクロを輸入してきておけば楽だ。起動は

clk1=> (run :dispose)

するか、-mainを起動してあげればよい。どうしても、マクロを持ってくるのがイヤなら

[sakae@manjaro gr]$ diff -u clock.clj clk2.clj
--- clock.clj   2014-09-23 06:07:43.000000000 +0900
+++ clk2.clj    2014-09-23 06:15:54.000000000 +0900
@@ -8,11 +8,10 @@
 ;   the terms of this license.
 ;   You must not remove this notice, or any other, from this software.

-(ns seesaw.test.examples.clock
+(ns clk2
   (:use seesaw.core
         seesaw.graphics
-        seesaw.color
-        seesaw.test.examples.example))
+        seesaw.color))

 ; A very rudimentary example of (canvas) that draws an analog clock

@@ -76,7 +75,7 @@
     ; Draw a little circle in the middle
     (draw g (circle 0 0 3) tick-style)))

-(defexample []
+(defn clk2 []
   (let [cvs (canvas :id :canvas :background "#BBBBBB" :paint paint-clock)
         t (timer (fn [e] (repaint! cvs)) :delay 1000)]
     (frame

強引にdefexampleをdefnにしてしまい、

clk2=> (invoke-later (-> (clk2) pack! show!))

とでもするかだな。で、実際に上記を走らせてみると、clockがメニューの中に張り付いて しまって、姿を表してくれなかった。同じ事をrpnの計算機でやってみたら、こちらは問題無し。 この差は何なの? こういう事に悩むのは無駄。素直にマクロを輸入しておけば問題無し。

Windowsでも発射台

一旦はWindowsで発射台強いてはexamplesの実行を諦めてしまった。けど、よく考えたら examplesの環境を決めてる、project.cljを調整すれば、発射台を設置出来るんではなかろうかと、 想像を巡らすようになった。

実験は1分もあれば出来るんで、やってみんべ。

C:\Users\sakae\Downloads\seesaw-develop>cat project.clj
(defproject seesaw "1.4.4-SNAPSHOT"
    :aliases { "examples" ["run" "-m" "seesaw.test.examples.launcher"]
               "clock"    ["run" "-m" "seesaw.test.examples.clock"]
               "rpn"      ["run" "-m" "seesaw.test.examples.rpn"]    }
  :dependencies [[seesaw "1.4.4"]]
)

こんなのを作っておいて、

C:\Users\sakae\Downloads\seesaw-develop>lein examples
C:\Users\sakae\Downloads\seesaw-develop>lein clock
C:\Users\sakae\Downloads\seesaw-develop>lein rpn

発射台も時計も計算機も無事に動いた。後は、GUIの技を盗むだけだな。