ClojureのSOAPライブラリ

Clojure用のSOAPライブラリを公開。
https://bitbucket.org/taka2ru/clj-soap

準備

Leiningenのproject.cljにこう書くだけで利用可。

project.clj
(defproject someproj "0.0.1-SNAPSHOT"
  :depenencies [[org.clojure/clojure "1.2.1"]
                [org.clojure/clojure-contrib "1.2.0"]
                [clj-soap "0.1.0"]]
  :aot [someproj.core]) ; src/someproj/core.cljでサービスを定義する場合。
$ lein deps

サーバを書く

サーバを書くには、defserviceを使う。

(ns someproj.core
  (:require [clj-soap.core :as soap]))

(soap/defservice jp.hoge.MyApp
  ^Integer (multiple [^Integer x ^Integer y] (* x y)))

(defn -main []
  (soap/serve "jp.hoge.MyApp")) ; 複数クラスがある場合は、並べて書けばいい。

※project.cljでAOTコンパイルを指定するのを忘れずに

$ lein compile
$ lein repl
user=> (use 'someproj.core :reload-all)
user=> (-main)

これで、 http://localhost:6060/axis2/services/ にアクセスしてみると、WSDLが見れる。

クライアント

client-fn にWSDLのURLを渡してやると、関数が帰ってくる。
関数の第1引数にメソッド名をキーワード指定し、以降に引数を並べてやれば呼び出しができる。

(let [cl (client-fn "http://localhost:6060/axis2/services/MyApp?wsdl")]
  (cl :multiple 10 20))

vimclojureとcakeのインストール

vimclojureとcakeで簡単にslime/swank的なことができる。
Ubuntu 11.04での設定を書いておく。

まあ、ここにも載ってるわけですが・・・
http://ubuntuforums.org/showthread.php?t=1746137

初期設定

まずは必要なパッケージをインストール
$ sudo apt-get install vim openjdk-6-jdk rubygems
$ sudo gem install cake
Cakeを起動できるようにbashエイリアスを設定

パスを通してもいいんだけど、一つだけなのでエイリアス

$ echo "alias cake='/var/lib/gems/1.8/bin/cake'" >> ~/.bashrc
$ exec bash
Vimclojure用のNailgunクライアントをインストール
$ mkdir -p ~/src
$ cd ~/src
$ wget http://kotka.de/projects/vimclojure/vimclojure-nailgun-client-2.2.0.zip
$ unzip vimclojure-nailgun-client-2.2.0.zip
$ cd ~/src/vimclojure-nailgun-client
$ make
$ sudo cp ng /usr/local/bin/
Vimclojureをインストール
$ cd ~/src
$ wget -O vimclojure-2.2.0.zip http://www.vim.org/scripts/download_script.php?src_id=13986
$ mkdir -p ~/src/vimclojure-2.2.0
$ cd ~/src/vimclojure-2.2.0
$ mkdir -p ~/.vim
$ cp -a autoload doc ftdetect ftplugin indent plugin syntax ~/.vim/
vimのファイルを編集

~/.vimrcを編集

syntax on
filetype indent plugin on

let g:vimclojure#WantNailgun = 1
let g:vimclojure#NailgunClient = "ng"
let g:vimclojure#HighlightBuiltins = 1
let g:vimclojure#ParenRainbow = 1
let g:vimclojure#DynamicHighlighting = 1
ドキュメントのタグ作成

vimを起動して下記を実行

:helptags ~/.vim/doc

これでドキュメントが表示できれば成功。

:help vimclojure

プロジェクトごとの作業

Cakeプロジェクトを作成
$ mkdir -p ~/work
$ cd ~/work
$ cake new hellocake
$ cd hellocake
VimclojureのNailgunサーバをプロジェクトに導入

project.cljを編集

(defproject hellocake "0.0.1-SNAPSHOT"
  :description "TODO: add summary of your project"
  :dependencies [[org.clojure/clojure "1.2.1"]]
  :dev-dependencies [[vimclojure/server "2.2.0"]]) ; ←これを追加
依存パッケージの取得
$ cake deps

編集時

Nailgunサーバの起動

一度起動すればプロセスが常駐します。

$ cake ng
NGServer started on 127.0.0.1, port 2113.
$
Vimで編集

Nailgunサーバが常駐した状態で、vimを起動して編集。

$ vi src/hellocake/core.clj

Vimclojureコマンド一覧

マニュアル丸写しですが。。。
通常のモードで下記を実行します。
の設定を変えている場合は、先頭は\ではなく設定した文字です。

評価
\et (EvalToplevel)
カーソル位置のトップレベル式を評価
\ef (EvalFile)
ファイル全体を評価
\eb (EvalBlock)
ビジュアルモードで選んだブロックを評価
\el (EvalLine)
現在の行を評価
\ep (EvalParagraph)
現在のパラグラフ
ロード
\rf (RequireFile)
ファイルをrequire (:reload)
\rF (RequireFileAll)
依存先を含めてファイルをrequire (:reload-all)
\rt (RunTests)
ファイルをrequireし、そのファイルをclojure.contrib.test-isを使ってテスト
マクロ展開
\me (MacroExpand)
カーソル位置を含むマクロ式を展開
\m1 (MacroExpand1)
カーソル位置を含むマクロ式を1段だけ展開
ドキュメント
\lw (DocLookupWord)
カーソル位置の単語のドキュメントを参照
\li (DocLookupInteractive)
対話的な入力でドキュメントを参照
\fd (FindDoc)
対話的な入力でドキュメントを検索
\jw (JavadocLookupWord)
カーソル位置のクラスのJavadocを外部ブラウザで開く
\ji (JavadocLookupInteractive)
対話的な入力でJavadocを探し、外部ブラウザで開く
ソース調査
\sw (SourceLookupWord)
カーソル位置の単語のソースを表示(参照のみ)
\si (SourceLookupInteractive)
対話的な入力でソースを表示(参照のみ)
\gw (GotoSourceWord)
カーソル位置の単語のソースへ移動
\gi (GotoSourceInteractive)
対話的な入力でソースへ移動
\mw (MetaLookupWord)
カーソル位置の単語のメタデータを参照
\mi (MetaLookupInteractive)
対話的な入力でメタデータを参照
REPL起動
\sr (StartRepl)
REPLを起動する
\sR (StartLocalRepl)
現在のバッファのnamespaceでREPLを起動する
Lispワード登録
\aw (AddToLispWords)
カーソル位置の単語をLispワードに登録し、特殊形式・マクロと同じインデントにする。(通常、ビルトイン以外は関数インデント)

Clozure CL + HunchentootのPOST時の文字化け対策

Clozure CL(Linux amd64)上でHunchentootを使用したときに、少しハマったので、メモしておく。

現象

POSTのパラメータに日本語を含めると、日本語部分だけが丸ごと消えてしまう。

原因

URLデコード処理のせいかもしれないと思って調べたら、やっぱりそうだった。

Clozure CL
 ? (hunchentoot::url-encode "あ" :utf-8)
"%C3%A3%C2%81%C2%82"  ; あれ?UTF-8じゃないぞ。
 ? (hunchentoot::form-url-encoded-list-to-alist '("abc=%C3%A3%C2%81%C2%82") :utf-8)
(("abc" . "あ"))
 ? (hunchentoot::form-url-encoded-list-to-alist '("abc=%E3%81%82") :utf-8)
(("abc" . ""))        ; 正しいUTF-8のコードはうまくデコードできない模様。
 ? (hunchentoot::form-url-encoded-list-to-alist '("abc=%E3%81%82") :latin-1)
(("abc" . "あ"))      ; external-formatをlatin-1にするとなぜかうまくいった。
SBCLだと正しく変換される
 * (hunchentoot::url-encode "あ" :utf-8)
"%E3%81%82"
 * (hunchentoot::form-url-encoded-list-to-alist '("abc=%E3%81%82") :utf-8)
(("abc" . "あ"))

対処

ハンドラの先頭で、external-format=latin-1でリクエストを再計算してやると、うまく取れる模様。

(defun some-handler ()
  #+ccl
  (progn
    (hunchentoot:no-cache)
    (hunchentoot:recompute-request-parameters :external-format :latin-1))
  ... ;以下、普通のハンドラの処理。
  )

Common LispでXMPPサーバコンポーネントを書くライブラリを作ってみた

https://bitbucket.org/taka2ru/cl-xmpp-ext/src

XMPPというのは、インスタントメッセージ(MSNメッセンジャみたいなやつ)のプロトコル。
Google Talkもこのプロトコルで通信をしており、クライアント次第でボイスチャットビデオチャットも乗せられるスグレモノ。
自分で拡張も定義できて、チャット以外の用途にも使える。リアルタイムでプッシュしたいものが向いている。HTTPはそういうの苦手*1

XMPPプロトコル自体にサーバを拡張する仕様があって、今回はそのプログラムを書くためのライブラリを書いた。
何か面白いものを作れないか妄想中。

*1:一応、Cometみたいなのでやれることはやれる。

Windows上のClozure CLでhunchentoot

http://d.hatena.ne.jp/t2ru/20101205/1291537110
のやり方でasdf-installを使えるようになったのは良いが、hunchentootをインストールしようとしたときにCL+SSLでエラーが出てしまう。

O_NONBLOCKというのがないと言われてた。

いいのかどうかは不明だが、下記の方法でとりあえずデフォルトページが動くところまではできた。

変更前
#+clozure-common-lisp
(defun install-nonblock-flag (fd)
  (ccl::fd-set-flags fd (logior (ccl::fd-get-flags fd) 
                     #.(read-from-string "#$O_NONBLOCK"))))
                     ;; read-from-string is necessary because
                     ;; CLISP and perhaps other Lisps are confused
                     ;; by #$, signaling"undefined dispatch character $", 
                     ;; even though the defun in conditionalized by 
                     ;; #+clozure-common-lisp
変更後
#+(and clozure-common-lisp (not win32))
(defun install-nonblock-flag (fd)
  (ccl::fd-set-flags fd (logior (ccl::fd-get-flags fd) 
                     #.(read-from-string "#$O_NONBLOCK"))))
                     ;; read-from-string is necessary because
                     ;; CLISP and perhaps other Lisps are confused
                     ;; by #$, signaling"undefined dispatch character $", 
                     ;; even though the defun in conditionalized by 
                     ;; #+clozure-common-lisp

Windows上のClozure CLでasdf-install

Windows上のClozure CLでasdf-installを使う方法について、先日のエントリのコメ欄で教えてもらった方法を試した。
http://d.hatena.ne.jp/t2ru/20100807/1281151524#c
意外に悶絶してしまったので、やり方を記録しておく。

~/ccl-init.lisp

;; asdf-installで使っているのに、CCLでは何故か入っていない。
(pushnew :win32 *features*)
(pushnew :mswindows *features*)

(require 'asdf)
(push "ccl:tools;asdf-install;" asdf:*central-registry*)

;; asdf-installでシンボリックリンクを貼れないので、パスをサーチするようにする。
(in-package #:asdf)
(defvar *subdir-search-registry*
  `(,(merge-pathnames ".asdf-install-dir/site/" (user-homedir-pathname))))
(defvar *subdir-search-wildcard* :wild)
(defun sysdef-subdir-search (system)
  (let ((latter-path (make-pathname :name (coerce-name system)
                                    :directory (list :relative *subdir-search-wildcard*)
                                    :type "asd"
                                    :version :newest
                                    :case :local)))
    (dolist (d *subdir-search-registry*)
      (let* ((wild-path (merge-pathnames latter-path d))
             (files (directory wild-path)))
        (when files
          (return (first files)))))))
(pushnew 'sysdef-subdir-search *system-definition-search-functions*)
(in-package #:cl-user)

asdf-install用に必要なパッケージを~/.ccl/startup/に手動で展開

  • gzip-stream
  • archive
  • trivial-gray-streams
  • flexi-streams
  • salza2

~/.asdf-install

;; asdf-installが使うパッケージは手動でロードできる状態にしておかないといけない。
(defparameter *asdf-startup-sites*
  (merge-pathnames ".ccl/startup/" (user-homedir-pathname)))

(dolist (p '("gzip-stream_0.2.8" "archive_0.8" "trivial-gray-streams-2008-11-02"
             "flexi-streams-1.0.7" "salza2-2.0.7"))
  (pushnew (merge-pathnames (concatenate 'string p "/") *asdf-startup-sites*)
           asdf:*central-registry*))

(asdf:oos 'asdf:load-op :gzip-stream)
(asdf:oos 'asdf:load-op :archive)

(defun asdf-install-extractor (to-dir tarball)
  (let ((name nil))
    (gzip-stream:with-open-gzip-file (ins tarball)
      (archive:with-open-archive (archive ins)
        (let ((*default-pathname-defaults* (pathname to-dir)))
          (archive:do-archive-entries (entry archive name)
            (archive:extract-entry archive entry)
            (unless name (setf name (archive:name entry)))))))
    ;; we use string instead of namestring because
    ;; asdf-install searches for /'s and not \'s
    ;; which will break on windows
    (string name)))

(push 'asdf-install-extractor asdf-install:*tar-extractors*)

;; suppress GPG checking
(setq asdf-install:*verify-gpg-signatures* nil)

これでasdf-installが動いた。

チルダクオート

マクロを組むときには変数捕捉に気をつけないといけないが、実は変数捕捉をうまく使う方法もあって、On Lispにそんな例が載っている。
http://www.komaba.utmc.or.jp/~flatline/onlispjhtml/anaphoricMacros.html

一番簡単な例としては、アナフォリックIFがある。

(defmacro aif (test-form then-form &optional else-form)
  `(let ((it ,test-form))
     (if it ,then-form ,else-form)))

(aif (big-long-calculation)
  (foo it))

この例では、比較結果である変数itを捕捉し、aifのthen-form内で使えるようにする。

Clojureでは少し長くなって、

(defmacro aif
  ([test-form then-form]
    `(aif ~test-form ~then-form nil))
  ([test-form then-form else-form]
    `(let [~'it ~test-form]
       (if ~'it ~then-form ~else-form))))

(aif (big-long-calculation)
  (foo it))

である。*1

アナフォラのitの前にチルダクオート(~')が付いているが、これはClojureのバッククオートがCommon Lispのものとは違う動作をするため。
Common Lispのバッククオートは、アンクオートできるクオートである。Clojureのバッククオートは、これに「名前空間解決&付加」という機能がプラスされている。
素のシンボルが欲しければ、クオートされたシンボルをアンクオート(すなわちチルダクオート(~'))しないといけない。

 * `(hoge list ,'list)
(HOGE LIST LIST)
user=> `(hoge list ~'list)
(user/hoge clojure.core/list list)

この違いは、マクロを使うときの名前衝突への対策のため。ちなみに、各言語での名前衝突への対策はこんな感じ:

  • Common Lisp : そもそもLisp-2*2なので変数名と関数名は衝突しないよ派
    • #'とかfuncallとかうぜぇ。
  • Scheme(R4RS〜) : 名前の衝突を自動回避するならLisp-1*3でも大丈夫だよ派
    • アナフォリックマクロみたいな、意図的に安全対策を破る(orサボる)ようなマクロは書けない。
    • Gaucheとかはdefine-macroが使えるけど、名前の衝突に関しては自己責任の部分がすごく増える。
  • Clojure : バッククオートの定義を変えて、read時に名前空間を付け足してしまえばLisp-1でも大丈夫だよ派
    • バッククオートがマクロ以外の用途に(ほぼ)使えなくなった。
    • macroexpandするとほとんどのシンボルに名前空間がくっついてくるので激しく見づらい。でっかいマクロを展開すると、画面がclojure.coreだらけになる(涙目)。

Clojureでは、バッククオートの定義を変えてしまったために、アナフォラを導入するときにチルダクオート(~')が必要になってしまったけど、逆に安全対策を破っていることを示すサインみたいになっていて、かえって良いかも知れない。

*1:if-letがあるからこんなのいらないよという話は置いといて。

*2:変数の名前空間と関数の名前空間が別々なので衝突しない。(その代わり、名前空間を行き来するオペレータ(symbol-functionとかfuncallとか)が必要)

*3:変数の名前空間と関数の名前空間が同じ。