ALGOBIT

2012/01/06

Clojure でも mapBetween

Filed under: 離散的な気まぐれ — タグ: , — Kohyama @ 11:02

404 Blog Not Found:algorithm – mapBetween – 配列の隣接する2項にそれぞれ演算を施した配列
を見たので clojure でどうなるかやってみました.

(defn map-between [f s] (map (fn [[a b]] (f a b)) (partition 2 1 s)))

実行例

user=> (map-between #(- %2 %1) '())
()
user=> (map-between #(- %2 %1) '(0))
()
user=> (map-between #(- %2 %1) '(0 1))
(1)
user=> (map-between #(- %2 %1) '(0 1 4))
(1 3)
user=> (map-between #(- %2 %1) '(0 1 4 9))
(1 3 5)

partition は便利で,
partition n step coll
で, シーケンス coll から, n 個ずつの要素のリストを, step 個ずつずらしながら取得できます.

user=> (partition 2 1 '(:a :b :c :d :e :f))
((:a :b) (:b :c) (:c :d) (:d :e) (:e :f))

[[a b]] の [a b] の部分はパターンマッチです.
引数が二要素のリストなら a に第一要素が, b に第二要素が束縛されますので,
元のシーケンスから partition で一個ずつずらしながら, 二個ずつ要素を取得し,
map で, そのそれぞれの二個の要素に対して, それを引数として二引数関数 f を適用し,
全体をシーケンスとして返します.

追記: 2011.01.06
@omasanori さんからのご指摘 で, 無駄に無名関数ひとつ挟んでいたのを直しました.
@omasanori さんありがとうございます.


Stuart Halloway
オーム社
発売日:2010-01-26

Clojure は JVM 上で動く LISP 処理系の一実装です.
文法は S 式ですが, 現代的な言語の良いところを集めて, 洗練された言語である上に, JVM の上で動き, Java クラスライブラリとの相互運用を可能にすることで, 若い言語でありながらライブラリが追いつく前から実用できるという成る程なやり方.
(この本を読んでいる間に純 Clojure のライブラリもどんどん充実し, Java との相互運用の必要性も薄れ, CLR 版も開発が進んでる様子ですが..)
言語が洗練されているので, この本は薄いですが, Clojure を理解するのに十分と思えます. ただし密度は濃いので流し読みはできません.
まずはこの本を, と思い, 他の Clojure 本は読んでませんが, これから他の本も読んでみたいと思います.

Amit Rathore
Manning Pubns Co
発売日:2011-11-28

パート1(七章まで)は, Clojure がどうなっているかの説明だ.
「どうしてそうなっているか」, 「(あなたが知っているかもしれない)他のやり方のどの欠点を克服するためにそうなっているか」を, 必要なところはパラダイムの説明に何節か割いたり, 他の言語でのそのパラダイムの取り扱われ方の説明も交え, 懇切丁寧に書かれている.
Clojure について, LISP について, 一般のプログラミング パラダイムについて, 知らなかったことについてはもちろん, 既に知っているものについても, より理解が深まると思う.

パート2(八章以降)は, 実用のケーススタディだ. 自分の必要とするケースに併せて拾い読みしても良いし, ケース自体の勉強のために通読しても良いと思う.

以下は, 前書きからの引用(拙訳)
“より良い道具とはなんだろう. 何に対してより良いということだろう.より良い道具とは複雑さの管理をより良く助けてくれるようなものだと私は思う.結局, 「複雑さ」が我々の業界のこんな状況の根本的な原因なのだ.ブルックスは1986年という早い時期に複雑さについて論文で言及している.彼は本質的な(essential) 複雑さと本質的でない (accidental) 複雑さの区別を書いている.
本質的な複雑さとは目的領域にそもそも存在するものであり, 一方,
本質的でない複雑さとは目的領域の外から持ち込まれた複雑さである.
例えば, 税金の申告を取り扱うソフトウェアプロジェクトにおいて,
入り組んだ税金コードに起因する複雑さはその領域の一部であり, つまり本質的である.
難解なビジター・パターンを採用するとか, そういったことが引き起こす全ての複雑さは
本質的ではないものだ.

先ほどの言明を言い換えさせてもらうならば, 「より良い道具とは本質的でない複雑さを
最小限にするよう我々を助けてくれるようなものだ」と言える.
我々が我々の仕事を可能な限り良い方法で行えるようにする.
すばらしい道具とはそれ以上のものだ. 我々に力を与えてくれる. 道具自体の問題を持ち込んだりせず, 設計者やプログラマとしての我々の有効性を増幅してくれる.
LISP プログラミング言語はそういった道具になるべく設計されている.
そして Clojure は驚くほど良く設計された LISP である.”


2011/10/05

ハノイとバベルの塔 第九階 – 端末アニメ in Clojure

方針

各手順を行った際の各杭の状態を順に出力したいので, 変化していく状態を, 状態のリストとして返したいです.
(第七階と同じです.)

状態の表現

各状態を, {各杭に積まれた円盤の番号を小さい順に並べたリスト} を三つの杭 A, B, C についてこの順に並べたリスト, とします.
例えば円盤の数 3 のハノイの塔の初期状態は

'((1 2 3) () ())

杭 A から杭 C に1 の円盤を移した状態は

'((2 3) () (1))

とします.

手順をあらわす関数

一手の円盤の移動に相当する関数を考えてみます.
A の杭の一番上の円盤を B の杭に動かす関数は

(fn [[[af & ar] b c]] (list ar (cons af b) c))

と書けます.
REPL で確かめてみます.

user=> ((fn [[[af & ar] b c]] (list ar (cons af b) c)) '((2 3) () (1)))
((3) (2) (1))

どの杭からどの杭に移すかを指定すると上記のような関数を返す関数を考えてみましょう.

(defn move [s d]
  (letfn [(rev [[a b c]]
            (if (< 0 (mod (- (+ 2 d) s) 3)) (list a c b) (list a b c)))
          (move01 [[[af & ar] b c]] (list ar (cons af b) c))]
    (fn [x]
      (->> x cycle (drop s) (take 3) rev move01
        rev cycle (drop (- 3 s)) (take 3)))))

->> は, フォーム列を与えると, 直前の結果を次のフォームの最後の引数として順に適用するマクロです.
s が 1, d が 0 (杭 B から, 杭 A に一番上の円盤を移動)として, ‘((3 4) (1 2) ()) を引数に与えた場合途中経過は以下のようになります.

(defn move [s d]
  (letfn [(rev [[a b c]]
            (if (< 0 (mod (- (+ 2 d) s) 3)) (list a c b) (list a b c)))
          (move01 [[[af & ar] b c]] (list ar (cons af b) c))]
    (fn [x]
      (->>
        x              ; '((3 4) (1 2) ())
        cycle          ; '((3 4) (1 2) () (3 4) (1 2) () ...)
        (drop s)       ; '((1 2) () (3 4) (1 2) () ...)
        (take 3)       ; '((1 2) () (3 4))
        rev            ; '((1 2) (3 4) ())
        move01         ; '((2) (1 3 4) ())
        rev            ; '((2) () (1 3 4))
        cycle          ; '((2) () (1 3 4) (2) () (1 3 4) ...)
        (drop (- 3 s)) ; '((1 3 4) (2) () (1 3 4) ...)
        (take 3)       ; '((1 3 4) (2) ())
      ))))

コードを toh09.clj に保存してあるものとして, REPL で確かめてみます.

user=> (load-file "toh09.clj")
#'user/-main
user=> ((move 0 1) '((1 2) () (3)))
((2) (1) (3))
user=> ((move 2 1) '((1 2) () (3)))
((1 2) (3) nil)
user=> ((move 2 0) '((1 2) () (3)))
((3 1 2) () nil)

空になった杭が nil になってしまいますが, cons に対して nil は空リストのように振る舞います

user=> (cons 1 nil)
(1)

ので気にしないことにします.

手順をあらわす関数のリスト

次に, 手順を表す関数のリストを返す関数を考えてみます.
円盤の数 n と, どの杭から(インデクス s) どの杭に (インデクス d) に移すかを与え, 先ほどの手順のリストを返します.
t は残りの杭のインデクスです.

(defn hanoi [s t d n]
  (if (zero? n) '()
      (concat (hanoi s d t (- n 1))
              (list (move s d))
              (hanoi t s d (- n 1)))))

s から d に n 番目以下の全ての円盤を移す手順の列は, s から t に (n – 1) 番以下の全ての円盤を移す手順の列と, s から d に一番上の円盤を移す手順と, t から d に (n – 1) 番以下の全ての円盤を移す手順を順に並べたものです.

(hanoi 0 1 2 n) で 目的の関数列が得られます.
(first (hanoi 0 1 2 n)) は最初の手順を表す関数です.
n == 3 として, 最初の手順を初期状態 ‘((1 2 3) () ()) に適用してみます.

user=> (load-file "toh09.clj")
#'user/-main
user=> ((first (hanoi 0 1 2 3)) '((1 2 3) () ()))
((2 3) () (1))

手順のリストを状態に順に適用

関数のリストと初期値を引数に取り, 初期値, 最初の関数を適用した結果, 前の結果に次の関数を適用した結果, … といった値の列を得るような関数について考えます.
つまり (list f0 f1 f2 …) と x を与えると
(list x (f0 x) (f1 (f0 x)) (f2 (f1 (f0 x))) … )
を返すような関数です.
これは

(defn iterf [[f & fs] x] (if f (cons x (iterf fs (f x))) (list x)))

のようにかけます.
やっていることは ->> と同じですが ->> はマクロなので,

(apply ->> (list x f0 f1 f2 ...))

のようには使えないと思いますたぶん.

円盤の数 n のハノイの塔を解く過程の全ての状態を出力するには

(iterf (hanoi 0 1 2 n) (list (range 1 (+ n 1)) '() '())

とすれば良いです.

user=> (load-file "toh09.clj")
#'user/-main
user=> (iterf (hanoi 0 1 2 3) (list (range 1 (+ 3 1)) '() '()))
(((1 2 3) () ()) ((2 3) () (1)) ((3) (2) (1)) ((3) (1 2) nil) (nil (1 2) (3)) ((1) (2) (3)) ((1) nil (2 3)) (nil nil (1 2 3)))

これで問題を解く部分は完成です.

整形

円盤の数 n と
‘((2 3) (1) ())
という状態を与えると

    |        |        |
  --|--      |        |
 ---|---    -|-       |
===========================

のような n + 1 行の文字列を返す関数を作ります.

(defn form [n s]
  (letfn [(ws [m] (take m (repeat \space)))
          (bar [m] (take m (repeat \-)))
          (pad0 [s] (map #(reverse (take n (concat (reverse %) (repeat 0)))) s))
          (rod [x] (concat (ws (+ (- n x) 1)) (bar x) "|"
                           (bar x) (ws (+ (- n x) 1))))
          (line [a b c] (concat (rod a) (rod b) (rod c) "\n"))
          (form_ [[[x & xs] [y & ys] [z & zs]]]
            (if x (concat (line x y z) (form_ (list xs ys zs)))
                  (concat (take (+ (* 6 n) 9) (repeat \=)) "\n")))]
     (apply str (form_ (pad0 s)))))

(ws [m]) は長さ m の空白文字, (bar [m]) は m 個のハイフンからなる文字列を返します.
pad0 は, 各状態を文字列で表現しやすいよう, 各杭を表すリストの先頭に長さが n になるまで に 0 を追加します.
n が 3 ならば, (pad0 ‘((2 3) (1) ())) は ‘((0 2 3) (0 0 1) (0 0 0)) となります.

(rod [x]) は杭を表す縦棒の両側に, 円盤の大きさ x 個のハイフンと, 余白の空白からなる文字列を返します.
n が 3 の時, (rod 1) は ” -|- “, (rod 3) は ” —|— ” のようになります.

(line [a b c]) は, ある行に相当する各杭の円盤の大きさから, 一行分の文字列を作ります.

form_ は, 各杭の状態を表すデータが格段の円盤の大きさ (無い場合は 0) を表すよう pad0 で変形された状態を受け取り, line で作った一行分の文字列と {各杭の次の段以後の円盤のを引数として呼び出した自分自身} と連結します.
最後の行は (concat (take (+ (* 6 n) 9) (repeat \=)) “\n”) で適当な数の ‘=’ にします.

確認

user=> (load-file "toh09.clj")
#'user/-main
user=> (print (form 3 '((1 2 3) () ())))
   -|-       |        |
  --|--      |        |
 ---|---     |        |
===========================
nil
user=> (print (form 3 '((2 3) (1) ())))
    |        |        |
  --|--      |        |
 ---|---    -|-       |
===========================
nil

main

(defn -main [& args]
  (let [argc (count args)
        n (if (< 0 argc) (Integer/parseInt (first args)) 3)
        w (if (< 1 argc) (Integer/parseInt (second args)) 500)]
    (dorun
      (map (fn [x]
             (print "\u001b[2J\u001b[2;1H")
             (print (form n x))
             (flush)
             (Thread/sleep w))
           (iterf (hanoi 0 1 2 n) (list (range 1 (+ n 1)) '() '()))))))

iterf と hanoi で作った状態のリストに対して順にを map の第一引数として指定した無名関数を適用します.
map は遅延シーケンスを返しますので, 副作用を全部処理するよう, dorun で包みます.
無名関数内では, エスケープシーケンスで端末をクリアし,
状態の表示を行い, flush でバッファをフラッシュし (しないと最終結果しか表示されません),
w ミリ秒停止します.

引数で, 円盤の数 n と, 各状態の表示の間の待ち時間 w (ミリ秒) を与えます.
指定しない場合は円盤の数 3, 待ち時間 500 ミリ秒とします.

クラスファイル等にコンパイルする場合は, -main がエントリポイントになるように調整します.
インタプリタで実行する場合は

;(apply -main *command-line-args*)

をアンコメントして, コマンドライン引数を -main に引き渡すようにします.

実行してみます.

ソースコード全体は以下のようになります.
toh09.clj

(defn move [s d]
  (letfn [(rev [[a b c]]
            (if (< 0 (mod (- (+ 2 d) s) 3)) (list a c b) (list a b c)))
          (move01 [[[af & ar] b c]] (list ar (cons af b) c))]
    (fn [x]
      (->> x cycle (drop s) (take 3) rev move01
        rev cycle (drop (- 3 s)) (take 3)))))

(defn hanoi [s t d n]
  (if (zero? n) '()
      (concat (hanoi s d t (- n 1))
              (list (move s d))
              (hanoi t s d (- n 1)))))

(defn iterf [[f & fs] x] (if f (cons x (iterf fs (f x))) (list x)))

(defn form [n s]
  (letfn [(ws [m] (take m (repeat \space)))
          (bar [m] (take m (repeat \-)))
          (pad0 [s] (map #(reverse (take n (concat (reverse %) (repeat 0)))) s))
          (rod [x] (concat (ws (+ (- n x) 1)) (bar x) "|"
                           (bar x) (ws (+ (- n x) 1))))
          (line [a b c] (concat (rod a) (rod b) (rod c) "\n"))
          (form_ [[[x & xs] [y & ys] [z & zs]]]
            (if x (concat (line x y z) (form_ (list xs ys zs)))
                  (concat (take (+ (* 6 n) 9) (repeat \=)) "\n")))]
     (apply str (form_ (pad0 s)))))

(defn -main [& args]
  (let [argc (count args)
        n (if (< 0 argc) (Integer/parseInt (first args)) 3)
        w (if (< 1 argc) (Integer/parseInt (second args)) 500)]
    (dorun
      (map (fn [x]
             (print "\u001b[2J\u001b[2;1H")
             (print (form n x))
             (flush)
             (Thread/sleep w))
           (iterf (hanoi 0 1 2 n) (list (range 1 (+ n 1)) '() '()))))))

;(apply -main *command-line-args*)

Stuart Halloway
オーム社
発売日:2010-01-26

Clojure は JVM 上で動く LISP 処理系の一実装です.
文法は S 式ですが, 現代的な言語の良いところを集めて, 洗練された言語である上に, JVM の上で動き, Java クラスライブラリとの相互運用を可能にすることで, 若い言語でありながらライブラリが追いつく前から実用できるという成る程なやり方.
(この本を読んでいる間に純 Clojure のライブラリもどんどん充実し, Java との相互運用の必要性も薄れ, CLR 版も開発が進んでる様子ですが..)
言語が洗練されているので, この本は薄いですが, Clojure を理解するのに十分と思えます. ただし密度は濃いので流し読みはできません.
まずはこの本を, と思い, 他の Clojure 本は読んでませんが, これから他の本も読んでみたいと思います.

2011/09/30

Swinging Clojure (3)

Filed under: 離散的な気まぐれ — タグ: , — Kohyama @ 12:57

Clojure から Swing を使います.
今回は, イベントの処理.

テキストフィールド二つ (TF1, TF2 とします) と, ボタン二つ (B1, B2 とします)を配置.
B1 が押されると, TF1 に書かれた文字列を TF2 にコピー.
B2 が押されると, TF1 と TF2 に書かれた文字列を消去します.
ボタンのラベルは “COPY” と “CLEAR” でも良いのですが, ボタンに表示される文字列と,
内部で使うコマンド用の文字列を区別するために少し長い名前にしてあります.

(import
  (java.awt FlowLayout)
  (java.awt.event ActionListener)
  (javax.swing JButton JFrame JTextField))

(let [
  tf1  (JTextField. 32)
  tf2  (JTextField. 32)
  b1   (JButton. "COPY TF1 to TF2")
  b2   (JButton. "CLEAR BOTH")
  f    (proxy [JFrame ActionListener]["Swinging Clojure (3)"]
         (actionPerformed [ae]
           (let [c (.getActionCommand ae)]
             (cond (= c "COPY") (.setText tf2 (.getText tf1))
                   (= c "CLEAR") (dorun (.setText tf1 "")
                                        (.setText tf2 ""))))))
  pane (.getContentPane f)]

  (.setLayout pane (FlowLayout.)) 

  ; commands of buttons
  (dorun (map
    (fn [[button command]]
      (doto button
        (.addActionListener f)
        (.setActionCommand command)))
    [[b1 "COPY"] [b2 "CLEAR"]]))

  ; layout
  (dorun (map #(.add pane %) [tf1 tf2 b1 b2]))

  ; frame
  (doto f
    (.setSize 400 300)
    (.setLocationByPlatform true)
    (.setDefaultCloseOperation JFrame/DISPOSE_ON_CLOSE)
    (.setVisible true)))

10行目 proxy は
(proxy [スーパークラス インターフェイス1 インターフェイス2 ... ] [スーパークラスのコンストラクタへの引数]
(メソッド名 [仮引数] ボディ)
… )
のように書きます.
スーパークラスを extends し, インターフェイスを implements する無名のクラス(内部的には clojure が他と重複しないよう, 適当な名前を付けてくれています.) を作成し, そのインスタンスを new して返してくれます.
インターフェイスに必要な全てのメソッドを定義しなくても構いません.
ここでは, JFrame を extends し, ActionListener を implements するクラス(
actionPerformed で ActionEvent に対する処理を記述します)を無名で作成し, そのインスタンスを f に束縛しています.
23行目で, 二つのボタンにこの f を addActionLisner でリスナーとして登録していますので, ボタンが押されたときは, proxy に書いておいた actionPerformed が(ActionEvent のインスタンスを引数として)呼ばれます.
その際, setActionCommand でボタンに登録しておいた文字列を, 12行目 getActionCommand で ActionEvent から取り出して, 処理を分岐しています.

proxy では,「スーパークラス」を省略することもできます(デフォルトで Object を継承します)ので, JFrame に ActionListener をさせずに, 別途 ActionListener の機能だけを持ったクラスのインスタンスを作り, ボタンのリスナーとして登録する場合は以下のようになります.

(import
  (java.awt FlowLayout)
  (java.awt.event ActionListener)
  (javax.swing JButton JFrame JTextField))

(let [
  f    (JFrame. "Swinging Clojure (3)")
  pane (.getContentPane f)
  tf1  (JTextField. 32)
  tf2  (JTextField. 32)
  b1   (JButton. "COPY TF1 to TF2")
  b2   (JButton. "CLEAR BOTH")
  al   (proxy [ActionListener][]
         (actionPerformed [ae]
           (let [c (.getActionCommand ae)]
             (cond (= c "COPY") (.setText tf2 (.getText tf1))
                   (= c "CLEAR") (dorun (.setText tf1 "")
                                        (.setText tf2 ""))))))]

  (.setLayout pane (FlowLayout.))

  ; commands of buttons
  (dorun (map
    (fn [[button command]]
      (doto button
        (.addActionListener al)
        (.setActionCommand command)))
    [[b1 "COPY"] [b2 "CLEAR"]]))

  ; layout
  (dorun (map #(.add pane %) [tf1 tf2 b1 b2]))

  ; frame
  (doto f
    (.setSize 400 300)
    (.setLocationByPlatform true)
    (.setDefaultCloseOperation JFrame/DISPOSE_ON_CLOSE)
    (.setVisible true)))

Swinging Clojure (2)

Filed under: 離散的な気まぐれ — タグ: , — Kohyama @ 10:39

Clojure から Swing を使います.
今回は, レイアウトの適用.

(import '(javax.swing JFrame JButton))
(import 'java.awt.BorderLayout)
(let [f (JFrame. "Swinging Clojure (2)")
      p (. f getContentPane)]
  (. p setLayout (BorderLayout.))
  (dorun
    (map
      (fn [[c l]] (. p add c l))
      [[(JButton. "PAGE_START") BorderLayout/PAGE_START]
       [(JButton. "LINE_START") BorderLayout/LINE_START]
       [(JButton. "CENTER")     BorderLayout/CENTER]
       [(JButton. "LINE_END")   BorderLayout/LINE_END]
       [(JButton. "PAGE_END")   BorderLayout/PAGE_END]]))
  (doto f
    (.setDefaultCloseOperation JFrame/EXIT_ON_CLOSE)
    (.setLocationByPlatform true)
    (.setSize 400 300)
    (.setVisible true)))

6行目〜 (dorun (map のところは,

(. p add (JButton. "PAGE_START") BorderLayout/PAGE_START)
(. p add (JButton. "LINE_START") BorderLayout/LINE_START)
(. p add (JButton. "CENTER")     BorderLayout/CENTER)
(. p add (JButton. "LINE_END")   BorderLayout/LINE_END)
(. p add (JButton. "PAGE_END")   BorderLayout/PAGE_END)

と同じで, これだけだと全部書いても記述量は変わりませんが, 共通部分をまとめる練習ということで.
map は遅延評価リストを返すので, そのままでは map が返した結果を直接または間接に出力しようとしないと, 中身は評価されません.
評価を強制するには dorun か doall を使います.
副作用のみが目的の今回は, map の返すリストを返さない dorun を使います.
(doall は map が返す結果を返します.)

13行目〜 doto [x & forms] は, x を forms の全てのフォームの二番目(つまりフォームが関数呼び出しなら, 最初の引数) の要素に挿入してくれる. つまり,

(.setDefaultCloseOperation f JFrame/EXIT_ON_CLOSE)
(.setLocationByPlatform f true)
(.setSize f 400 300)
(.setVisible f true))

ということ.
これだけだと記述量は変わらないけど, 練習ということで.

(. f setDefaultCloseOperation JFrame/EXIT_ON_CLOSE)
(. f setLocationByPlatform true)
(. f setSize 400 300)
(. f setVisible true))

と書いても同じ.

BorderLayout やその他のレイアウトの詳細は
How to Use BorderLayout の辺りが分かりやすいです.

Swinging Clojure (1)

Filed under: 離散的な気まぐれ — タグ: , — Kohyama @ 10:02

Clojure のいいところの一つは, 組み込みの GUI ツールキットがあるところです.
その名も Swing.
JVM の上で動き, Java のクラスライブラリが使えるため, javax.swing ももちろん使える訳です.
なるほど.

まずは, 最小限コード.
ラベル一つを貼付けたウィンドウを開くだけ.

(import '(javax.swing JFrame JLabel))

(let [f (JFrame. "Swinging Clojure (1)")
      p (. f getContentPane)]
  (. p add (JLabel. "Hello Swinging Clojure!"))
  (. f setDefaultCloseOperation JFrame/EXIT_ON_CLOSE)
  (. f setLocationByPlatform true)
  (. f setSize 200 100)
  (. f setVisible true))

6 〜 8行目 はこの時点でどうしても必要というものではありませんが, ウィンドウを開く時の基本設定ですので, やっつけときます.
setDefaultCloseOperation で, ウィンドウが閉じられた時の挙動を指定します. EXIT_ON_CLOSE は, exit で, アプリケーションを終了します. REPL で実行しているならば REPL が終了します.
デフォルトは HIDE_ON_CLOSE で, リソースを解放せずに, ウィンドウを隠します. setVisible(false) 相当ですので, 後で setVisible(true) すればもう一度ウィンドウを開けます.
デフォルトでは毎回同じ位置にウィンドウが開きます. 大抵のウィンドウシステムは, ウィンドウを開くときに少しずつずれた位置に表示して, 完全に重ならないようにしてくれる機能がありますが, この機能を有効にするには, setLocationByPlatform() に true を指定します.
setSize は名前から想像できる通りです.

Swing は知っています. という方には以下の java コードとほぼ同等と思っていただければ良いかと思います.
Hello.java

import java.awt.Container;
import javax.swing.JFrame;
import javax.swing.JLabel;

public class Hello {
    public static void main(String[] args) {
        JFrame f = new JFrame("Swinging Clojure (1)");
        Container p = f.getContentPane();

        p.add(new JLabel("Hello Swinging Clojure!"));
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setLocationByPlatform(true);
        f.setSize(200, 100);
        f.setVisible(true);
    }
}

2011/09/06

clojure から DBMS を利用する

Filed under: 離散的な気まぐれ — タグ: , , , , , , , — Kohyama @ 14:11

準備

アクセスしたい DBMS 製品の JDBC ドライバを取得し,
JRE が標準でサーチする外部クラスライブラリの配置場所に加えます.

Oracle の場合は, このへん (Oracle 10g の場合) から,
MySQL の場合は, このへん から,
PostgreSQL の場合は, このへん から,
JRE (もしくは JDK) のバージョンおよび, DBMS 製品のバージョンに適合するバージョンのJDBCドライバファイル (大抵は zip か jar でアーカイブされた単一のファイル) を入手します.

Windows の場合は, C:\Program Files\Java\jre6\lib\ext (JRE 1.6 の場合) あたり,
Linux の場合は, /usr/lib/jvm/java-6-sun/jre/lib/ext/ (Ubuntu の場合) あたり,
MacOS X の場合は, /Library/Java/Extensions/ あたり,
に上記 zip または jar ファイルとして入手した JDBC ドライバをコピーします.

もちろん, DBMS がローカルにインストールされており, JDBCドライバが既に存在するのでコピーしたくないとか, 現在のプロジェクトを管理するフォルダ階層の下に置いておきたい,
などの理由で標準サーチパスに置きたくない場合は, クラスパスにドライバファイルへの絶対パスもしくは相対パスを追加しても構いません.

最小限

データベースアクセスに必要な最低限の記述を部品化せず全て展開した状態で書くと
以下のようになります.

(use 'clojure.contrib.sql)
(with-connection {:classname "oracle.jdbc.OracleDriver"
                  :subprotocol "oracle"
                  :subname "thin:@${HOST}:${PORT}:${SID}"
                  :user "${USER}"
                  :password "${PASSWORD}"}
  ; ここでの記述はデータベース ${HOST}, ${PORT}, ${SID}
  ; に対するトランザクションの中で実行される.
  (with-query-results rs [${QUERY}]
    ; ここでの記述はデータベースに対する ${QUERY} の実行結果
    ; を rs にシーケンスとして束縛した状態で行われる
    ))

${HOST}, ${PORT}, ${SID} にアクセスしたいデータベースに関する情報,
${USER}, ${PASSWORD} にそのデータベースアクセスするアカウントの情報,
${QUERY} には発行したい SQL 文,
でそれぞれを直接置き換えてください.

MySQL の場合は

    :classname “com.mysql.jdbc.Driver”
    :subprotocol “mysql”
    :subname “//${HOST}:${PORT}/${SID}”

とします.

PostgreSQL の場合は

    :classname "org.postgresql.Driver"
    :subprotocol "postgresql"
    :subname "//${HOST}:${PORT}/${SID}"

とします.

結果セットに対して, トランザクション内で作業するのではなく,
ベクタとして取り出してしまいたい場合は,
with-query-results の本体で (into [] rs) とすれば OK.

例えば

IP アドレス 192.168.1.1 で稼働する, ポート 1521 でアクセス可能な Oracle に,
パスワード PaSsWoRd の kohyama というユーザがあり,
kohyamadb というインスタンスへの select 権限があるとします.
JRE 1.6 (ともちろん clojure) のインストールされた Windows から, kohyamadb のテーブル一覧を見るには
上記サイトからダウンロードした classes12.jar を
C:\Program Files\Java\jre6\lib\ext\ にコピーし

(use 'clojure.contrib.sql)
(with-connection {:classname "oracle.jdbc.OracleDriver"
                  :subprotocol "oracle"
                  :subname "thin:@192.168.1.1:1521:kohyamadb"
                  :user "kohyama"
                  :password "PaSsWoRd"}
  (with-query-results rs ["select * from all_tables"]
    (into [] rs)))

とします.
簡単過ぎる...

部品化例

私の場合はこんな風にして使いました.

(use 'clojure.contrib.sql)
(defn db [& {:keys [dbms host port sid user password]}]
  (conj
    (cond
      (= dbms "oracle")
        {:classname "oracle.jdbc.OracleDriver"
         :subprotocol "oracle"
         :subname (str "thin:@" host ":" port ":" sid)}
      (= dbms "mysql")
        {:classname "com.mysql.jdbc.Driver"
         :subprotocol "mysql"
         :subname (str "//" host ":" port "/" sid)}
      (= dbms "postgresql")
        {:classname "org.postgresql.Driver"
         :subprotocol "postgresql"
         :subname (str "//" host ":" port "/" sid)})
    {:user user
     :password password}))

(def con
  (db :dbms "oracle" :host "192.168.1.1" :port 1521 :sid "kohyamadb"
    :user "kohyama" :password "PaSsWoRd"))

(def all_tables
  (with-connection con
    (with-query-results rs ["select * from all_tables"]
      (into [] rs))))

(print all_tables)

関数 db は, DBMS 製品の名称, ホスト名, ポート, インスタンス名, ユーザ名,
パスワードをキーワード引数で渡すと,
with-connection に渡すべきマップを作成します.
19〜21行目で特定のDBに対する特定のアカウントでの接続用のマップを作成します.
23〜26 行目でトランザクションを実行し結果を取り出します.

Copyright © 2010 Yoshinori Kohyama All Rights Reserved.