2009年12月16日水曜日

zope.sqlalchemyの紹介

中西さんよりZope/Ploneアドベントカレンダーのバトンを頂戴したので、Zope/Ploneネタを書いて16日目を担当したいと思います。

普段の仕事ではZope/PloneよりもSQLAlchemyなどを使いつつRDBMSを使用するようなフレームワークを使うことの方が多いです。
どんなネタにしようかとすこし悩みましたが、Zope/PloneからSQLAlchemy越しにRDBMSにアクセスする方法について調べてみました。

Zopeは自前のオブジェクトデータベースZODBを持っているのでZope/PloneからRDBMSを使用する機会はあまりありませんが、既存のデータベースとやりとりが必要になることは、たまにあります。
ZopeからRDBMSを操作する方法としては、
を使用するが昔からあって、
select * from products where id=<dtml-sqlvar type="string">
このような感じで使用するようですが、ちょっと古い感じがしないでもないです。

現在PythonでSQLとなれば、
The Python SQL Toolkit and Object Relational Mapperであるところの
SQLAlchemyを使うのが一般的です。
ということで、zope.sqlalchemyを使う方法を調べてみました。
zope.sqlalchemyは、"Minimal Zope/SQLAlchemy transaction integration"ということで
ZopeのトランザクションとSQLAlchemyのトランザクションを統合してくれる
ツールのようで、その際、きちんと二相コミットもしてくれるようです。
zope.sqlalchemyの守備範囲は、トランザクションに関してだけで、データベースエンジンやデータモデルの記述については特別なサポートはありません。
つまり、SQLAlchemyの作法に則って、DBエンジンやモデルについての記述を行うことはできるけれども、ZMIからデータベースエンジンの設定を行ったり、zcmlで定義したり、Zopeのオブジェクトモデルとマッピングしたり、といった機能はなく、あくまでトランザクションの統合機能だけが提供されます。
>>> from sqlalchemy import create_engine
>>> engine = create_engine("mysql://hoge", convert_unicode=True)
>>> from sqlalchemy.orm import scoped_session, sessionmaker]
>>> from zope.sqlalchemy import ZopeTransactionExtension
>>> Session = scoped_session(sessionmaker(bind=engine,
... twophase=True, extension=ZopeTransactionExtension()))
>>> session = Session()
>>> session.add(Hoge(name='fuga'))
>>> session.query(Hoge).all()
[]
>>> import transaction
>>> transaction.commit()

とこんな感じの使い方になるようです。
(詳しくはzope.sqlalchemyを参照してください。)
ポイントは、
sessionmaker(bind=engine,
twophase=True, extension=ZopeTransactionExtension())

の部分と
>>> import transaction
>>> transaction.commit()

のところです。


コードの説明に入るまえに、そもそもなぜトランザクションの統合が必要になるか、ということについて簡単に説明します。

Wikipediaのトランザクションの項によると、
"トランザクション (transaction) とは、分ける事の出来ない一連の情報処理の単位である"とあります。
つまりひとつのトランザクションに含まれる処理であれば、全部が成功もしくは全部が失敗のどちらかになるべきで、一部が失敗して一部が成功した、といった状態になったら困るわけです。これを実現するために、ひとつの
トランザクション処理の途中で、なんらかの不都合が発生した場合、そこまでの処理をすべてなかったことにするロールバックや、すべての処理が成功した段階で、変更結果を永続化させるコミットの機能がZODBやRDBMSにより提供されます。

ZopeのZODBはトランザクションをサポートしており、作法に則ったやり方でZODBにアクセスすれば、処理の途中で例外が発生した場合ロールバックされ、一貫性が保たれます。
しかし、そこでロールバックされるのは当然ZODBに対する書き込み操作のみで、もし直接外部のRDBMSに対して書き込みを行っていた場合、その処理まで自動でロールバックされるわけではありません。すると、Zope/Ploneとしては処理が途中で失敗したのでロールバックを行い、ZODBの中身的には変更が無かったことになっているのに、外部のRDBMS には処理が成功したかのように変更が書き込まれてしまう、といった結果になる可能性があります。多くの場合において、これは望んだ結果ではないでしょう。
これを防ぐには、ZODBとRDBMSに対するふたつのトランザクションを統合し、ZODBに対する処理とRDBMSに対する処理が、両方成功するか、両方失敗するか、どちらかであるようにする必要があります。

これを実現する中核となるのが、zope.sqlalchemy.ZopeTransactionExtensionです。これはSQLAlchemyの
extending-sessionという機能を利用したもので、
sqlalchemy.orm.interfaces.SessionExtensionを継承(or実装。ここらへんはPythonなので微妙なところですね。)したクラスです。このクラスを使用することにより、zope.sqlalchemyはセッションの開始や、セッションの中でオブジェクトの追加や変更、削除などをフックすることができます。
でもって、セッションの開始でTwoPhaseSessionDataManagerインスタンスを作成し、zope_transaction.get().join(TwoPhaseSessionDataManager(session, state))
のような感じでzopeのトランザクションに結びつけます。
そして、SQLAlchemyのセッションでオブジェクトの変更などをフックして、状態を初期状態のSTATUS_ACTIVEからSTATUS_CHANGEDに変更します。
そんでもって
>>> import transaction
>>> transaction.commit()

によりzopeのトランザクションがコミットされた場合は、TwoPhaseSessionDataManagerのメソッドが呼び出され、
もし状態がSTATUS_CHANGEDになっていれば、二相コミットを行います。



STATUS_ACTIVE/STATUS_CHANGEDと、2相コミットに関して補足します。

2相コミットとは、複数のデータベースが存在する時に、各トランザクションを統合して、全体として成功/失敗のどちらかにするための技術です。ちょっと説明が適当なので、
詳しくは
Wikipedia:"2相コミット"を参照してください。
例えば、データベースAとBがあった場合、よくありそうな問題としては、全部の処理がうまくいったので、
まずAにコミットして成功し、つぎにBに対してコミットを行おうとしたら、コミットに失敗してしまった、という状況です。
これを防ぐために2相コミットでは、トランザクションのコミットしようとするとき、
参加しているデータベースに対して、コミットが可能であるか、まず全員に質問します。ここで、全員から同意が得られれば、全体をあらためてコミットします。逆に、どれかひとつでも同意が得られなかった場合は、全体をロールバックさせます。この2相コミットを使用すれば、DBMSが落ちる、DBMSとのネットワークが切れるなどの事態が肝心なタイミングで発生しなければ、全体の一貫性を保つことができます。
この2相コミットは、それなりに面倒な処理です。RDBMSの方に書き込みを行わなかった場合、そもそも2相コミットを行う必要がありません。このためにzope.sqlalchemyでは、SQLAlchemyのセッションに変更操作を行ったかどうかを、STATUS_ACTIVE/STATUS_CHANGEDという状態により管理し、zopeのtransaction.commit()時にSTATUS_ACTIVEのままであれば2相コミットを省略するような動きになっているようです。
STATUS_ACTIVE/STATUS_CHANGEDの状態は、sqlalchemy.orm.interfaces.SessionExtensionのafter_bulk_updateメソッドなどが呼び出されるタイミングで、STATUS_CHANGEDに変更されます。
このフックメソッドは、次のようにORMを通さないでSQLが発行された場合は呼び出されません。
(よびだしてくれればいいような気がしますが。)

>>> conn = session.connection()
>>> conn.execute(update_expression)

この問題を防ぐため、このような場合には、つぎのように明示的にSTATUS_CHANGEDをセットします。
>>> from zope.sqlalchemy import mark_changed
>>> mark_changed(session)

<追記 2009-12-18>
sessionmakerの作成の際に、twophase=Falseにしておけば、2相コミットをせずに
動いてくれます。当然不整合が起こる可能性は高くなりますが。
あと、twophase=Trueの場合は、当然ですがRDBMS側でも2相コミットが可能な設定になっている必要があります。

ということで、ということで、次のバトンは昔からブログでお世話になっているPythonの大先輩のnakagamiさんにお願いします。

2009年12月6日日曜日

InfoQ ClojarsとLeiningenを使ったClojure向け自動ライブラリ依存関係管理

PythonのPYPI、easy_install/pipにあたるもののようだ。

slime + swank-clojureの続き

slime + swank-clojureが、やっぱり調子が悪く、
どうもClojure1.1との組み合わせの問題のようなので
http://github.com/stuarthalloway/swank-clojure/tree/clojure-1.1
のswank-clojureを使ったらうまく動いた。




(追記)
結局
http://github.com/technomancy/swank-clojure/tree/clojure-1.1
の方に変更した。

2009年12月5日土曜日

slime + swank-clojureが動かない

Clojure with Emacs and Slime/Swank on Ubuntuを参考にemacs + slime + swank-clojureでプログラミングしてるのだけれど、
最近のslimeとswank-clojureの組み合わせがこける。
SLIME REPL broken
を参考に、

"/home/hokari/opt/swank-clojure/src/main/clojure/swank/core/protocol.clj"


@@ -4,6 +4,20 @@

 ;; Read forms

+  (def #^{:private true}

+     *percent-re* #"%")

+


+(defn- fix-percent

+  "Replace double colons with a /."

+  ([text] (.replaceAll (re-matcher *percent-re* text) "?")))

+

+(def #^{:private true}

+     *double-colon-re* #"::")


+

+(defn- fix-double-colon
+  "Replace double colons with a /."

+  ([text] (.replaceAll (re-matcher *double-colon-re* text) "/")))

+

(def #^{:private true}

      *namespace-re* #"(^\(:emacs-rex \([a-zA-Z][a-zA-Z0-9]+):")


 (defn- fix-namespace

@@ -50,7 +64,7 @@

   ([#^java.io.Reader reader]

      (let [len  (Integer/parseInt (read-chars reader 6 read-fail-exception) 16)

            msg  (read-chars reader len read-fail-exception)


-           form (read-string (fix-namespace msg))]

+           form (read-string (fix-namespace (fix-double-colon (fix-percent msg))))]

        (if (seq? form)


          (deep-replace {'t true} form)

          form))))

のようなかんじにしたら動いた。
Common LispとClojureで"%"や"::"の意味とか許可/不許可が違うせい、
ということみたい。

2009年11月16日月曜日

ClojureのvectorやlistをJavaの配列にキャストする方法

ClojureからJavaのメソッドを呼び出すときなどに、
ClojureのvectorからString[]やint[]などにキャストする必要がある場合、
to-arrayやinto-arrayを使うとキャストできる。

user> (to-array ["a" "hello"])
#
user> (into-array ["a" "hello"])
#
user> (into-array String ["a" "hello"])
#
user> (into-array Number [1 1.2])
#
user> (into-array [1 2])
#
user> (into-array [1 1.2]) ;これはエラーに
; Evaluation aborted.

2009年6月5日金曜日

UsedのThinkpad X60s購入

UsedのThinkpad X60sを¥39,800で購入しました。ちなみに英語キーボードです。
Ubuntuを入れて快調です。メモリは2G追加。
インストールはUSBメモリからで、ひっかかることもなく
とても簡単にできました。無線LANもデフォルトで使用できました。
右パームレスト部分が無線LANカードの発熱のために熱くなりますが、
これは仕様なようです。無線OFFにしておくと熱くならなかったし、
無線もともと使わない予定なので問題なし。
久方ぶりにマイノートPCを手に入れ感無量。
半年くらいしたらSSDにしたいなぁと妄想は膨らむ。

2009年5月31日日曜日

JavaOne 2009 Script Bowl

JavaOne 2009では、去年のJavaOne 2008に引き続きScript Bowlのセッションがあるようです。
Script Bowlでは、JavaVM上のスクリプト言語同士でプログラミング対決をしたり、
言語の特長を紹介したりするようです。
去年は、Scala,Groovy,JRuby, Jythonだったのですが、
今年はこれにClojureが加わって5つの言語で対決するようです。
JavaVM上のスクリプト言語は、他にも山ほどあるわけで、
JavaOneで5つめに選ばれたってことは、けっこう注目度がたかいということでしょうか。
これをきっかけにさらに盛り上がるといいなぁ。

2009年5月19日火曜日

Clojure勉強の進捗

1.Clojureでマクロ
マクロの練習に、パターンマッチングのマクロを書いた。
マクロの基本的な書き方や、mapやvector、sequenceの基本もわかってきた。

gitの使い方を覚えて、antの使い方の基礎を覚えたら、
githubにのせてみたい。

2.Clojureでモナド
Clojure.contrib.monadsの使い方を下記の解説をみて覚える。
Monads in Clojure
A monad tutorial for Clojure programmers (part 1)
part 2
part 3
part 4

http://stefan-klinger.de/
のところにある
The Haskell Programmer's Guide to the IO Monad — Don't Panic.
をもう一度読む。
Arrow syntax
8.10. アロー記法
も読んでArrowも理解したい。

3.その他
軽くJavaの復習。antやJDEEなども。
あとConcurrent-Programming-Java-TM-Principles
を注文したのできたら読む。

LWGL - Lightweight Java Game Library
というのを見つけた。よさそう。

2009年5月10日日曜日

Tokyo CabinetをUbuntuにインストール

Tokyo Cabinet
Tokyo CabinetはHirabayashi Mikio 氏により新しく実装された高速なDBMです。

DBMというのは、Database Managerの略で、非常にシンプルなキー:バリュー型の
データベースです。プログラミングでいうところのハッシュマップや連想配列、辞書と同じような機能を、ファイル上に永続化して使用できるというようなものです。
Berkeley DBなどが有名です。

Tokyo Cabinetは、とても新しく実装されたDBMで、
高速な処理や非常に大きいデータベースファイルを扱えるなどのすぐれた
特徴を持ち、Java,Perl,Ruby,Lua用のAPIが公式に用意されています。

ClojureでオレオレDBMSを実装するためのバックエンドとして使いたいと思っているので、
今回はまず、UbuntuにTokyo CabinetとJava APIをインストールします。

sudo apt-get update
wget http://tokyocabinet.sourceforge.net/tokyocabinet-1.4.20.tar.gz
sudo apt-get install build-essential
sudo apt-get install zlib1g-dev
sudo apt-get install libbz2-dev

wget http://tokyocabinet.sourceforge.net/tokyocabinet-1.4.20.tar.gz
tar xzvf tokyocabinet-1.4.20.tar.gz
cd tokyocabinet-1.4.20
export CPPFLAGS="-I/usr/lib/jvm/java-6-openjdk/include/"
export JAVA_HOME=/usr/lib/jvm/java-6-openjdk
export PATH=$JAVA_HOME/bin:$PATH

./configure
make

ここで前回インストールしたPacoを使用して
sudo make install

のかわりに、
sudo paco -D make install

としてインストールしてみた。
つぎにJava用APIをインストール。

wget http://tokyocabinet.sourceforge.net/javapkg/tokyocabinet-java-1.18.tar.gz
tar xzvf tokyocabinet-java-1.18.tar.gz
cd tokyocabinet-java-1.18
./configure
make
make check
sudo paco -D make install


http://tokyocabinet.sourceforge.net/javadoc/
にあるサンプルを実行。

import tokyocabinet.*;
% cat TCHDBEX.java
import tokyocabinet.*;

public class TCHDBEX {
public static void main(String[] args){
HDB hdb = new HDB();
if(!hdb.open("casket.tch", HDB.OWRITER | HDB.OCREAT)){
int ecode = hdb.ecode();
System.err.println("open error: " + hdb.errmsg(ecode));
}
if(!hdb.put("foo", "hop") ||
!hdb.put("bar", "step") ||
!hdb.put("baz", "jump")){
int ecode = hdb.ecode();
System.err.println("put error: " + hdb.errmsg(ecode));
}
String value = hdb.get("foo");
if(value != null){
System.out.println(value);
} else {
int ecode = hdb.ecode();
System.err.println("get error: " + hdb.errmsg(ecode));
}
hdb.iterinit();
String key;
while((key = hdb.iternext2()) != null){
value = hdb.get(key);
if(value != null){
System.out.println(key + ":" + value);
}
}
if(!hdb.close()){
int ecode = hdb.ecode();
System.err.println("close error: " + hdb.errmsg(ecode));
}

}
}

% javac TCHDBEX.java
% ls
TCHDBEX.class TCHDBEX.java% java TCHDBEX
hop
foo:hop
bar:step
baz:jump

% ls
TCHDBEX.class TCHDBEX.java casket.tch


インストールは成功した模様。

PacoをUbuntuにインストール

"make install"したソフトウェアを管理できる超便利ツール「Paco」
を参考にPacoをUbuntuにインストール。

sudo apt-get update
sudo apt-get install build-essential
sudo apt-get install libgtkmm-2.4-dev
wget http://downloads.sourceforge.net/paco/paco-2.0.6.tar.gz
tar zxvf paco-2.0.6.tar.gz
cd paco-2.0.6/
./configure
make
sudo make install
sudo make logme

2009年5月8日金曜日

Clojure 1.0

Clojure 1.0 - Clojure Clojure | Google Groups
Clojure: Clojure 1.0News about Clojure by Rich Hickey

ついにClojureの1.0がでました。
Congratulation!

2009年5月4日月曜日

zsh上でsvnの補完がエラーになる

zsh上でsvnコマンドの補完が
_arguments:comparguments:303: invalid argument: ARG
というメッセージとともにエラーになる症状になった。
どうも、subversion1.5とzsh4.3.4の組み合わせで、
zsh4.3.4の
/usr/share/zsh/4.3.4/functions/_subversion
が、subversion1.5での変更に対応していないことが原因のよう。
http://d.hatena.ne.jp/sotarok/20080813/1218649287
こちらを参考に、

% curl -O http://gvn.googlecode.com/svn/trunk/contrib/zsh/_subversion
% sudo mv _subversion /usr/share/zsh/site-functions

で解決しました。
ありがとうございます。

2009年5月1日金曜日

ScalaをUbuntuにインストール

UbuntuにScalaをインストール。
http://lovehateubuntu.blogspot.com/2008/05/adventures-in-scala-part-i.html
http://d.hatena.ne.jp/riue/20080105/1199552108
を参考にしました。

Javaは前回のClojureのインストール時に
sudo apt-get install openjdk-6-jdk
でインストール済み。
wget http://www.scala-lang.org/downloads/distrib/files/scala-2.7.4.final.tgz
tar xzvf scala-2.7.4.final.tgz
sudo mv scala-2.7.4.final /usr/share/scala
sudo ln -s /usr/share/scala/bin/scala /usr/local/bin/scala
sudo ln -s /usr/share/scala/bin/scalac /usr/local/bin/scalac

ビルドをしていないので、
ダウンロードしてパスを通しただけです。
.emacsに
(add-to-list 'load-path "/usr/share/scala/misc/scala-tool-support/emacs")
(require 'scala-mode-auto)
(setq scala-interpreter "/usr/local/bin/scala")

2009年4月29日水曜日

Clojureの紹介

Clojureの特徴。

-JavaVMの上で動く
-Lispの新しい方言
-動的型付けの純粋でない関数型言語
-STMによる並行処理
-豊富でimmutableなデータ型
-遅延シーケンス

というところあたり。

1.JavaVMの上で動く
JavaVMの上で動く言語は、ScalaやGroovy、ABCLなど
たくさんある。

これらの中でClojureは新しいほう。
JavaVMの上での言語開発の利点は、Javaとの連携が容易で
Javaの豊富なライブラリを利用できる点と、
JVMの高いパフォーマンス、マルチプラットフォーム対応
ガーベージコレクタなどの恩恵を受けられる点など。

2.Lispの新しい方言

Common LispやR5RSなどの仕様によらない、
新しく設計されたLispの方言。
当然コードはS式でマクロが使える。
新しいLispという意味では、ポールグレアムのArcなどと同様ですね。
JVM上のLispは、Common Lisp on JVMのABCL、
Scheme on JVM のSISCやKAWAなどがあるが、
新規設計のLisp方言onJVMはClojure以外にいまのところなさそう。
リーダマクロが独自定義できない、
末尾再帰最適化がないなどの理由から、
Common Lispなどからきたユーザには窮屈に感じられそう。

Lisp以外からきたユーザには、Common LispやSchemeより
逆にとっつきが良いかもしれない。

主観的な感想だが、
Common Lispの無骨な感じや歴史からくる複雑さ、
Schemeの原理主義的な感じから比べて、
Clojureには、PythonやRubyのような
ユーザに優しい雰囲気が感じられる。

3.動的型付けの純粋でない関数型言語
Haskellなどのような純粋関数型ではないが、
immutableなデータ型とそれへの参照を使って状態が管理されるので、
Python,Common Lisp などに比べると、
比較的破壊的な操作が少ない。

基本はimmutableで、プロセスディクショナリやetsを持っている
Erlangとポジションは似ているかも。

ここまでは前提条件的なもの。
ここからが、とくにClojureの魅力的な点。

4.STMによる並行処理

STMは、Software Transactional Memory
wikipedia:ソフトウェアトランザクショナルメモリ
複数スレッドからの共有メモリの読み書きを、
データベースのトランザクション処理のように制御する方法。
"Beautiful concurrency" Simon Peyton Jones (PDF)
や、書籍「ビューティフルコード」など。
並行制御として、ErlangやScalaのようなアクターモデルと
このSTMと、あとはロックを使う方法などがあって、
どれが有用なのかについては、議論の的になっているようだ。

5.豊富でimmutableなデータ型

データ型の便利さにPythonに近いものを感じる。
Erlangの持つimmutableでファーストクラスのコンテナは、
リストとタプルしかない。
dictはリストの組み合わせでしかなく、リレラルもない。
これはErlangの弱点だと思われる。
また、immutableなコンテナは、パフォーマンスの低下を
招きかねないが、そこらへんも効率よく実装されているらしい。

6.遅延シーケンス
Clojureはもちろん遅延評価ではないが、
遅延シーケンスが言語レベルでサポートされている。
Pythonのイテレータやジェネレータの感覚にとても近いものを感じるが、
遅延シーケンス自体もimmutableなので、状態を内在している
Pythonのジェネレータより扱いやすい点もありそう。
ここは要調査。

Clojureのlistとvector初見

Clojureは便利な組み込みのデータ型を備えている。
これらはぜんぶが全部immutable、変更不可能になっている。
でも参照を扱う仕組みが別にあって、参照先が変更可能なので、
ErlangやHaskellより命令的に破壊的な操作?を書ける。
そこはSTMと絡んでくるので後回し。

Clojureは動的型で、Pythonの組み込み関数typeと同じように
型について調べる事ができる。
user> (type 1)
java.lang.Integer

整数や文字列などはJavaのクラスがそのまま使われているようだ。
JavaのVMの仕組みはわからないけれど、
Javaは静的型なので、こういったインスタンスやクラスの情報は
なんらかの形でラップされて扱われているんだろうと思われる。
型もふつうのオブジェクトのようで、比較ができる。
user> (= (type 1) (type 3))
true
user> (= (type 1) (type 30000000000000000000000))
false

typeのtype、Pythonだとメタクラスになるところだが、
おなじようなものだろう。
user> (type (type 1))
java.lang.Class
user> (type (type (type 1)))
java.lang.Class

文字列や、大きい整数もこんな具合。
user> (type "abc")
java.lang.String
user> (type 10000000000000)
java.lang.Long
user> (type 100000000000000000000000000)
java.math.BigInteger

コンテナ型は、Clojure独自に定義されている。
immutableということで、PersistentHoge
という名前が付けられている。
user> (type [])
clojure.lang.PersistentVector

これはPersistentVector。単にvectorと呼ばれることの方が多い。
IPersistentVectorというのは明らかにインターフェースと思われるが
MultiMethodsと関係してくるのだろうか?今は不明。

user> (type ())
clojure.lang.PersistentList$EmptyList

こっちはPersistentList。EmptyListとついているのが気になる。
これがうわさのMetadataか?
listとvectorが別にある。vectorの方が可変長配列でlistはリンクリスト
ってことだろうか。
listは当然だがvectorにもリテラルが用意されている。
user> [1 2 3 4]
[1 2 3 4]
user> [1, 2, 3, 4]
[1 2 3 4]

カンマは、あっても無くても同じ。
好き嫌いが分かれそうだが、僕は嫌いじゃない。
関数で生成することもできる。
user> (vector 1 2 3 4)
[1 2 3 4]
user> (vector)
[]

listのほうはこんなかんじ。
user> '(1 2 3 4)
(1 2 3 4)
user> '(1, 2, 3, 4)
(1 2 3 4)
user> (list 1 2 3 4)
(1 2 3 4)

Lispですね。

Clojureのインストール(Ubuntu 9.04)

Clojure on Ubuntu
を参考にUbuntu 9.04にClojureをインストール。
sudo apt-get install openjdk-6-jdk
sudo apt-get install ant subversion git-core
mkdir ~/opt
cd ~/opt
svn co http://clojure.googlecode.com/svn/trunk clojure
svn up
cd clojure
ant
mkdir ~/.clojure
cp clojure.jar ~/.clojure
cd ~/.clojure
java -cp clojure.jar clojure.lang.Repl
user=> (+ 1 2)
3
cd ~/opt
git clone git://github.com/kevinoneill/clojure-contrib.git
cd clojure-contrib
git pull
ant -Dclojure.jar=../clojure/clojure.jar
cp *.jar ~/.clojure
emacs ~/.profile
=============
export CLOJURE_EXT=~/.clojure
PATH=$PATH:~/opt/clojure-contrib/launchers/bash
alias clj=clj-env-dir
=============

user=> (System/getProperty "java.class.path")
"/home/xxx/.clojure/clojure.jar
:/home/xxx/.clojure/clojure-contrib-slim.jar
:/home/xxx/.clojure/clojure-contrib.jar"