2013-11-17

コマンドのハードコーディングを回避する仕組み

/bin/sh決め打ちとかそういうのを回避するために使う。こういうの、他の人も色々書いてる気がするけど。

(defvar alternate-program-table
  (make-hash-table :test 'equal))

(defmacro define-alternate-program (program alt)
  `(puthash ,program ,alt alternate-program-table))

(defun alternate-program (program)
  (gethash program alternate-program-table))

(defadvice start-process (around kludge-for-hard-coding (name buffer program &rest program-args) activate)
  (let* ((alternate (alternate-program program))
         (program (or (if (consp alternate) (car alternate) alternate) program))
         (program-args (if (consp alternate) (append (cdr alternate) program-args) program-args)))
    ad-do-it))

こういう感じに使う。

(defmacro when-windows (&rest body)
  `(if-windows (progn ,@body)))

(when-windows
  (define-alternate-program "/bin/sh" '("fakecygpty" "/bin/sh")))

Cygwin環境とfakecygptyがちゃんとあれば、term.elのansi-termとかがWindowsでもきちんと動作するように。

2013-03-03

HelmとSKKのキーバインドの競合

正確に言うとHelmじゃなくって、helm-files.elが読み込むdired-x.elSKKがC-x C-jのキーバインドで競合する。

(setq dired-bind-jump nil)

Helmを読み込む前に書いておくとC-x C-jにdired-xの機能が割り当てられなくなるため、競合を回避できる。dired-bind-jumpのdocstring曰く

Non-nil means bind `dired-jump' to C-x C-j, otherwise do not.
Setting this variable directly after dired-x is loaded has no effect -
use \\[customize].

とのことなので、くれぐれも設定する位置には注意すること。

この問題自体は既知の問題らしくて、「dired-x C-x C-j」で検索するといくつも情報が出てくる。

2013-01-17

LinuxでのJava SE JDKインストール手順メモ

多少手順が必要だったので現時点での注意点を自分用にメモ。

まず、検索で最初に引っかかるjava.comからはJREしかダウンロードできないので、JDKをダウンロードするのはOTNのJavaのページから。(java.comには開発者向けのJDKの案内やリンクなどはないように見える)案内に従って、米国のサイト経由で自分の環境に合ったパッケージ、あるいはアーカイブをダウンロードする。

ダウンロードが終わったら、パッケージを利用する場合はパッケージ管理ツールを使い、手動でインストールする場合はアーカイブを展開して/optなどに手動でインストール。詳細はダウンロードのページからインストールのガイドにリンクが張ってあると思われるのでそれを参照。JDK 7の場合は"JDK 7 Installation for Linux Platforms"。

その後は、手動でJava SEのバージョンを管理するのであれば、シンボリックリンクなども利用しつつ、PATH環境変数を利用して管理。dpkg由来のupdate-alternativesで自動化するのであれば、

% update-alternatives --get-selections

でJavaに関連する既存のグループを調べつつ、

% sudo update-alternatives --install /usr/bin/java java /usr/java/jdk1.7.0_11/bin/java 40
% sudo update-alternatives --config java
% sudo update-alternatives --install /usr/lib64/browser-plugins/javaplugin.so javaplugin /usr/java/jdk1.7.0_11/jre/lib/amd64/libnpjp2.so 40
% sudo update-alternatives --config javaplugin

といった感じで設定を変更する。手動で設定を切り替えるなら、優先度の値は何でも構わない。

2013-01-16

Ruby on Rails

色々とあって、Ruby on Railsを試しているところなんだけれど、ウェブアプリケーションを作るときに必要になる部分がかなり高度に抽象化、自動化されている感じで、人気がある理由が少し理解できた気がした。ここまで洗練させるにはかなりの労力が必要だったんじゃないかと考えると、開発してきた人たちを素直に称賛したい所存。

そしてMVCアーキテクチャ。こういう方向性のフレームワークに触るのはMFC以来かも。もう記憶が曖昧。

今だと、こういうので作ったアプリケーションを公開するのにHerokuとかのPaaSを使ったりする感じだろうか。良い時代だと思う。割と今更感溢れる感想だけど。

2012-09-25

percent-encoding

Qiitaに投稿したパーセントエンコーディングについてのTipsにも書いたんだけど、自分の知ってるライブラリはどうも帯に短し襷に長しといった感じで、

  • application/x-www-form-urlencodedも扱える(似ているものなので、同じライブラリで扱いたい)
  • ASCIIに収録されていない文字を任意のエンコーディングでオクテット列に変換し、パーセントエンコーディングできる(今なら大体UTF-8で済みそうだけど、できれば他のエンコーディングも扱いたい)

という条件のものは見つからず、色々試行錯誤していた。

そんな折に@snmstsさんが教えてくださったのがdo-urlencodeで、普通のパーセントエンコーディングもapplication/x-www-form-urlencodedも両方扱えるし、UTF-8決め打ちとはいえ自分はUTF-8くらいしか使わないだろうから良いか、ということで使ってみた。

それで、使ってみて気付いたんだけど、どうもメモリの消費がちょっと多い。そしてちょっと遅い。

(let ((str (map 'string (lambda (_)
                          (declare (ignore _))
                          (code-char (random 255)))
                (make-string (* 1024 1024)))))
  (null (time (puri::encode-escaped-encoding str puri::*reserved-characters* t)))
  (null (time (urlencode:urlencode str))))

;; CCL 1.8
;; (PURI::ENCODE-ESCAPED-ENCODING STR PURI::*RESERVED-CHARACTERS* T)
;; took 82,384 microseconds (0.082384 seconds) to run.
;; 13,839 microseconds (0.013839 seconds, 16.80%) of which was spent in GC.
;; During that period, and with 2 available CPU cores,
;; 64,990 microseconds (0.064990 seconds) were spent in user mode
;; 13,998 microseconds (0.013998 seconds) were spent in system mode
;; 18,647,856 bytes of memory allocated.
;; 40 minor page faults, 0 major page faults, 0 swaps.
;; (URLENCODE STR)
;; took 7,272,771 microseconds (7.272771 seconds) to run.
;; 1,337,318 microseconds (1.337318 seconds, 18.39%) of which was spent in GC.
;; During that period, and with 2 available CPU cores,
;; 7,079,924 microseconds (7.079924 seconds) were spent in user mode
;; 171,974 microseconds (0.171974 seconds) were spent in system mode
;; 1,178,938,384 bytes of memory allocated.
;; 264 minor page faults, 0 major page faults, 0 swaps.

;; SBCL 1.0.58
;; Evaluation took:
;; 0.053 seconds of real time
;; 0.052993 seconds of total run time (0.046993 user, 0.006000 system)
;; 100.00% CPU
;; 124,622,974 processor cycles
;; 18,647,216 bytes consed
;;
;; Evaluation took:
;; 3.412 seconds of real time
;; 3.395483 seconds of total run time (3.268503 user, 0.126980 system)
;; [ Run times consist of 0.404 seconds GC time, and 2.992 seconds non-GC time. ]
;; 99.50% CPU
;; 7,962,815,308 processor cycles
;; 770,256,208 bytes consed

これはpuriのエンコーダと比較した結果なんだけど、4MB分の文字列を扱うのにCCLで1GB超、SBCLで700MB超のメモリを使っている。ワーオ。

コードを読んだところ、どうもエスケープ一回ごとに文字列とベクタのアロケーションが起きるようになっていた。ここを何とかすればメモリ消費量や処理速度が改善できるんじゃないだろうかうへへ、ということで改造することに。

で、試行錯誤の結果、大幅にメモリ消費量と速度は改善した。したんだけど、どうも中身が完全に別物になってしまった。総書き換えといった感じで、これを作者に取り込むように主張しても、「お前これ同じなの関数のインターフェイスだけやん」と言われる未来が容易に想像できたので、仕方なく別のライブラリに仕立てることにした。「自分で書いてしまったりしがち」とかQiitaのTipsに書いておいてこれだから割と救いようがない。

そうと決めたらもう開き直って、UTF-8以外のエンコーディングのサポートとか、puriで見かけて良いと思った、どの文字をエスケープするか指定できる機能とかも付けてみた。その結果がこれGitHubのミラーもある。

普通に使う分には難しいことはなく、

(percent:encode str)

とすればエンコードできるし、

(percent:decode str)

とすればデコードできる。エンコードやデコードする文字を指定する場合はこんな感じ。

;; 特定の文字だけエンコードしたり
(percent:encode "/usr/bin/["
                :test (lambda (x)
                        (or (percent:unreservedp x)
                            (= x #x2f))))    ; '/'
;=> "/usr/bin/%5B"

;; 特定の文字だけデコードできる
(percent:decode (percent:encode "a:b/c" :test (constantly nil))
                :test #'percent:alphap)
;=> "a%3Ab%2Fc"

こんなことできて何が嬉しいの? という風に思うかもしれないけど、URIはそれぞれのコンポーネントで許される文字が違うので、そういうのに対応するときに便利。あとはする必要がない文字までエンコードしてるURIの正規化とか、規格に沿わない実装に対応するのにも使える。詳しい使い方はREADMEやユニットテストを参照のこと。

あんまり需要はないと思うけど、自分で使うために書いたので問題ない。将来的にもっと良さげなライブラリを見つけたらそっちを使うかも。というか、誰か書いてください。

2012-09-09

続々sequentially-apply

ネタはさておき、実際のコードで使ってみた例とかも載せておく。

;; before
(defun sha1 (obj)
  (let* ((string (encode-json-to-string obj))
         (octets (string-to-octets string))
         (digest (digest-sequence :sha1 octets)))
    (byte-array-to-hex-string digest)))

;; after
(defun sha1 (obj)
  (sequentially-apply (encode-json-to-string obj)
    (string-to-octets _)
    (digest-sequence :sha1 _)
    (byte-array-to-hex-string _)))

誤差だと思えてならないが、相対的にすっきりはしている。気になるのは、コードを読むときに視点が右上から左下に動くこと。微妙に読み辛い?

(defun sha1 (obj)
  (pipe (encode-json-to-string obj)
        (string-to-octets _)
        (digest-sequence :sha1 _)
        (byte-array-to-hex-string _)))

みたいに揃ってる方が、視点が上から下って感じで見易い気もする。

2012-09-08

cl-fn 0.2

自分自身でもすっかり投げっぱなしで忘れていた関数ユーティリティライブラリ、cl-fnの0.2をリリースした。

  • sequentially-apply
  • ->
  • ->>

というマクロが新たに追加されている。

インストールするには、

% cd ~/quicklisp/local-projects
% wget -q -O - https://bitbucket.org/llibra/cl-fn/downloads/cl-fn-0.2.tar.bz2 | bzcat | tar xf -

みたいな感じで。普通にブラウザとかでダウンロードしてきても、最終的にQuicklispのlocal-projectsディレクトリの下でアーカイブを展開すれば大丈夫。もちろん、Mercurial使ってる人はhg cloneでもオーケー。Git使ってる人はGitHubのリポジトリがあるのでどうぞ。自分以外に使う人がいるとも思えないけど一応。

quicklisp/local-projectsの下に展開し終わったら、

> (ql:quickload :cl-fn)

すればcl-fnの関数やマクロが使えるようになる。パッケージ名はそのまんまcl-fn。

で、本題。新しく導入したマクロの話。

->->>Clojureから借りてきたもの。使い方はリンク先に載っているので読んでほしい。日本語の解説を読みたい方は「Clojureの->と->>の使い方 - あと味」辺りが詳しいのでどうぞ。簡単に説明すると、関数の戻り値を順番に適用していきたい場合に便利なマクロ。->は最初の引数、->>は最後の引数として戻り値を渡す。

sequentially-apply->->>の亜種みたいなものだけど、こちらはprogsを参考にして作った。オリジナルとの違いは、戻り値を渡す位置を「_」で指定できるようにしているのと、多値に対応していること。こんな風に使う。

(sequentially-apply (loop for n from 1 to 10 collect n)
  (reduce #'+ _)
  (print _))
;-> 55

ひとつ前の式の戻り値を使いたい場所に「_」を指定しておくと、戻り値に置き換わる感じ。

多値を使う場合はこうする。

(sequentially-apply (values 1 2 3)
  (mapcar (lambda (x) (* x 2)) (list _ _ _))
  (reduce #'+ _))
;=> 12

それぞれの値が順番に「_」の部分に展開されると考えて問題ない。ちなみに、順番の入れ替えは現状ではできないので注意。入れ替えたい場合は

(sequentially-apply (values 1 2 3)
  (let ((x _) (y _) (z _)) (list z y x)))
;=> (3 2 1)

(sequentially-apply (values 1 2 3)
  ((lambda (x y z) (list z y x)) _ _ _))
;=> (3 2 1)

のようにletlambdaを経由することになる。

ついでに、sequentially-applyについて考えたこと、考えていることとかも書いておく。

引数を「_」で指定することについては、タイプ数が少し増えるけど、->->>みたいに複数のマクロを作る必要がないこと、それぞれの式が本来の呼び出しの形に似ていることからこうしてある。その辺りが好みじゃなければ->->>を使ってくださいというスタンス。

多値の順番の入れ替えについては、「_1」「_2」みたいに順番を指定する、みたいなことは割と簡単にできるけど、どうなんだろう。自分的にはそんなにない状況な気がするし、letで良いんじゃ、とか思うけど、良くわからない。不便だと思ったらいつか対応するかも。ただ、「_1」と「_」が混ざったらどうするの、とか考えると割と微妙かもしれない。

名前については、良い感じの短い名前が思い付かなかった。個人的に記号だけの識別子は好きじゃないから避けたいけど、pipeとかも微妙に感じるし……。将来的に変えるかもしれない。記号が駄目なら->とかはどうなんだ、という話だけど、そっちも良い名前が思い付いたら変えるかも。ただ、元の->とかも別名として残すとは思う。逆にsequentially-applyの方も、長いから「>>」っていう別名付けてよ、みたいに誰か他の人が言ってきたら多分対応する。まあ、まずあり得ないからこっちは気にしない。

こんなところかな。lambdaの代わりのfn(「_」で引数無視できて楽)とか、定番のcurryrcurrycomposeconjoindisjoinflipとか、(setf (symbol-function ...) ...)の代わりのdefaliasとか、あとは今回の->->>sequentially-applyとか、そういうのを求めてる人は気が向いたらどうぞ。

2012-09-05

OCamlのtoplevelを再起動するための設定

#quit;;してM-x tuareg-run-ocamlするのが面倒になってきたので。Emacs Lispは詳しくないから間違った作法とかがあるかも。(特にプロセスの終了を待つあたりとか)

(defun restart-ocaml-toplevel ()
  (interactive)
  (let ((buffer (get-buffer tuareg-interactive-buffer-name)))
    (cond ((null buffer) (tuareg-run-ocaml))
          (t
           (let ((process (get-buffer-process buffer)))
             (when process
               (comint-simple-send process "#quit;;")
               (with-timeout (10)
                 (while (comint-check-proc buffer)
                   (sleep-for 0 500)))))
           (goto-char (point-max))
           (tuareg-run-ocaml)))))

(defun introduce-tuareg-toplevel-restart ()
  (define-key tuareg-mode-map "\C-cr" 'restart-ocaml-toplevel))

(add-hook 'tuareg-mode-hook 'introduce-tuareg-toplevel-restart)

2012-08-25

OCamlSpotterを強引に使うための設定

CygwinでビルドしたOCaml 4.00.0と、それを使ってビルドしたOCamlSpotter(bf0060bc031f)と、non-Cygwin Emacs 23.2を併用するためのkluge。CygwinでのパスとWindowsのパスの表現の違いによる問題を解決するものなので、MinGW版のOCamlならこういう設定は不要のはず。多分素直に使える。

.emacsの(require 'ocamlspot)以降に

(defun cygpath (args)
  (let* ((command (with-output-to-string
                    (princ "cygpath")
                    (mapc (lambda (x) (princ " ") (princ x)) args)))
         (result (shell-command-to-string command)))
    (replace-regexp-in-string "\n$" "" result)))

(defun ocamlspot-query-string-at-cursor ()
  (let ((file-name (cygpath `(-u ,(prin1-to-string (buffer-file-name))))))
    (setq ad-return-value
          (format "%s:l%dc%d"
                  file-name
                  (ocamlspot-lines-of-point)
                  (ocamlspot-bytes-of-line-to-point)))))

(defun ocamlspot-find-file-existing (path)
  (let ((path (cygpath `(-w ,path))))
    (if (file-exists-p path)
        (find-file-other-window path)
      (ocamlspot-message-add (format "ERROR: source file %s was not found" path))
      nil)))

で取り敢えず動くと思う。良く分からなかったりちゃんと動かなかったら諦めた方が吉。

やってて思ったけど、素直に仮想マシンにLinuxなりをインストールした方が苦労が少なそう。でもこのPCメモリそんな積んでないから厳しい。

2012-08-23

パッケージに対する雑感

よく他の言語で、コードを読みにくくするので、特定の名前空間を丸ごと取り込む(OCamlのopen MとかHaskellのimport MとかC++のusing Nとか)のは要注意って言われるけど、Common Lispの場合どれだけuse-packageしても、

(symbol-package 'x)

ホームパッケージを確認できるから、気軽にシンボルインポートできる。これは、識別子(シンボル)がファーストクラスオブジェクトなことによる大きな利点だと思う。運用で回避したり(名前空間を取り込む代わりに、短い別名を付けてアクセスすることが多い)、ツールで補助する必要があまりない。

また、名前空間(パッケージ)自体もファーストクラスなオブジェクトなので、どういうシンボルが含まれるかとか、他から取り込んでいるシンボルはどれかとか、そういうことをコードの字面から読み取る情報としてではなく、計算可能なデータとして扱うことができる。これも大きなアドバンテージじゃないだろうか。Lispの動的、対話的に開発する文化が良く出ている機能だとも思う。

対して、少し扱いづらいと感じる部分もある。例えば、OCamlにはlocal modulelocal openという機能があって、

(* ListモジュールにLという別名を付ける *)
let module L = List in L.iter print_string [ "a"; "b"; "c" ]

(* 部分的にListモジュールをopenする *)
let open List in iter print_string [ "a"; "b"; "c" ]

のようなことができるけど、こういう粒度の細かい名前空間の切り替えは、リーダーとパッケージの仕様の都合上、ANSI Common Lispの範囲ではできないと思う。(リーダーマクロを使えば実現できそう)

ちなみに、部分的にuse-packageするのとは少し違うけど、Allegro CLSBCL(1.0.55以降)には、

ppcre::(split ":" "a:b:c")

のように、指定したパッケージをカレントパッケージにして式を評価する構文がある。ただし、式の中ではパッケージが切り替わっているので、

cl-user::(let ((x 0)) cl::(print x))    ; cl::(...)の中のxの参照はcl:xの参照と解釈される

こういうことはできない。

他にも、シンボルが衝突するなどの理由でパッケージをuse-packageしたくない、でもこの長いパッケージ名を毎回明示するのは厳しい、という場合、

(let ((nicknames (package-nicknames :too-long-package-name)))
  (rename-package :too-long-package-name
                  :too-long-package-name
                  (cons :short-name nicknames)))

なんて感じで、新しい別名を付けたりすると思う。書いているコードがアプリケーションなら特に問題はないんだけど、それがライブラリだったとき困る。ライブラリをロードするたびに特定のパッケージに別名を付けてしまうのは、いかにも行儀が悪い気がするし、その別名が他のパッケージ名と衝突なんてした暁には、ライブラリの利用者に対処してもらうことになって色々悲しい。

(defparameter *old-nicknames* (package-nicknames :too-long-package-name))

(rename-package :too-long-package-name
                :too-long-package-name
                (cons :short-name *old-nicknames*))

...

(rename-package :too-long-package-name
                :too-long-package-name
                *old-nicknames*)

のようにすることはできるけど、これを毎回書くのもどうなんだという気もする。この辺りの事情を改善するためのものがFrancois-Rene Rideauのpackage-renamingだと思うんだけど、READMEを読む限りでは、少しトリッキーな感じで他の人には勧めにくい。

とまあ、物足りなく感じる部分もあるけど、Common Lispのパッケージがとても柔軟で強力な仕組みなのは間違いないと思う。特に、対話的に開発する場合には威力を発揮する。……と、他の言語を使っていて改めて実感した次第。

2012-08-17

Caml modeの設定

Caml modeの設定も。

(autoload 'caml-mode "caml" "Major mode for editing OCaml code." t)
(autoload 'run-caml "inf-caml" "Run an inferior OCaml process." t)
(autoload 'camldebug "camldebug" "Run ocamldebug on program." t)

(setq inferior-caml-program
      (if-windows "sh -c ocaml" "ocaml")
      auto-mode-alist
      (cons '("\\.ml[iylp]?$" . caml-mode) auto-mode-alist)
      interpreter-mode-alist
      `(("ocamlrun" . caml-mode)
        ("ocaml" . caml-mode)
        ,@interpreter-mode-alist)
      process-coding-system-alist
      (cons (if-windows '("^sh$" utf-8 . utf-8)
                        '("ocaml" utf-8 . utf-8))
            process-coding-system-alist))

;; If "ocamlc -where" is called, OCaml built with Cygwin returns a Cygwin-style
;; path. Caml mode on non-Cygwin Emacs can't handle it.
(when-windows
  (setq ocaml-lib-path '("C:/Cygwin/usr/local/ocaml-4.00.0/lib/ocaml")))

(add-hook 'caml-mode-hook 'viper-mode)

(if window-system
    (require 'caml-font))

inferior-caml-program"sh -c ocaml"にしているのは、OCamlのインストール先の都合上。/usr/local/ocaml-4.00.0以下にインストールして、/usr/local/binなどに各ファイルのシンボリックリンクを張っているんだけど、CygwinをリンクしてないEmacsだとシンボリックリンクを辿れないし、パスを追加したりとかは色々面倒なので。

shのprocess coding systemをここで指定しているのは格好悪いかも。どうせCygwinは1.7以降UTF-8前提なんだし、別の所で堂々と設定したほうが良いかもしれない。

Tuareg modeの設定

現時点での設定。

(load "tuareg-site-file")

(setq tuareg-use-smie t
      tuareg-interactive-program inferior-caml-program
      tuareg-browser 'browse-url-firefox
      tuareg-library-path
      (if-windows "C:/Cygwin/usr/local/ocaml-4.00.0/lib/ocaml/"
                  "/usr/local/lib/ocaml/"))

(defun customize-tuareg-face ()
  (set-face-attribute 'tuareg-font-lock-governing-face nil
                      :foreground (face-attribute 'font-lock-keyword-face :foreground)
                      :weight 'normal)
  (set-face-attribute 'tuareg-font-lock-operator-face nil
                      :foreground "#999999"))

(defun set-tuareg-compile-command ()
  (set (make-local-variable 'compile-command) "omake"))

(when window-system
  (add-hook 'tuareg-mode-hook 'customize-tuareg-face))

(add-hook 'tuareg-mode-hook 'set-tuareg-compile-command)
(add-hook 'tuareg-mode-hook 'viper-mode)

ちなみに、if-windows

(defmacro if-windows (then &optional else)
  `(if (eq window-system 'w32)
       ,then ,else))

こういうマクロ。

2012-08-11

OMakeをビルドするときの問題

OMake 0.9.8.6をCygwinでビルドするとき、

  • 依存しているライブラリが正常に検出されない
  • OCaml 4.00.0でビルドすると警告が原因でビルドに失敗する

という問題があったので、解決方法を記録しておく。

依存するライブラリが検出されない問題について。OMakeでは、自身をビルドするとき、ncursesなどのライブラリの検出にCheckLibという関数を利用している。このCheckLibの実体は、lib/configure/Configure.omで定義されている、CheckCLibという関数なんだけど、ここでライブラリの指定をLDFLAGSではなく、CFLAGSでしている。

public.CheckCLib(libs, funs) =
    CFLAGS += $(addprefix -l, $(libs))

    return $(TryLinkC $"""
#ifdef __cplusplus
extern "C"
#endif
#pragma warning( disable : 4100 )
/* Override any gcc2 internal prototype to avoid an error.  */
$(add-wrapper $(nl)extern char , $'();', $(funs))
int main(int argc, char **argv) {
    /* Usage */
$(add-wrapper $(nl)    , $'();', $(funs))
    return 0;
}
""")

これによって、gccに渡される-lオプションがCFLAGSの位置にきてしまい、ライブラリを検出するときのリンクに失敗する。

なので、

--- ../omake-0.9.8.6-/lib/configure/Configure.om        2012-08-11 15:05:41.265625000 +0900
+++ lib/configure/Configure.om  2012-08-11 16:12:42.359375000 +0900
@@ -266,7 +266,8 @@
 # \end{doc}
 #
 public.CheckCLib(libs, funs) =
-    CFLAGS += $(addprefix -l, $(libs))
+    # CFLAGS += $(addprefix -l, $(libs))
+    LDFLAGS += $(addprefix -l, $(libs))

     return $(TryLinkC $"""
 #ifdef __cplusplus

のように修正すれば、正常にライブラリが検出されるようになる。

ちなみに、FAM(OMakeの-Pオプションを利用するために必要)のためにCygwinのパッケージに収録されているGaminをインストールした場合、ビルドに失敗してしまう。

- build src/main omake.opt
+ ocamlopt.opt -warn-error A -w Aekr-29z -I . -I ../libmojave -I ../util -I ../magic -I ../ast -I ../ir -I ../env -I ../exec -I ../eval -I ../shell -I ../build -I ../builtin -o omake.opt unix.cmxa ../libmojave/lm.cmxa ../util/util.cmxa ../magic/magic.cmxa ../ast/ast.cmxa ../ir/ir.cmxa ../env/env.cmxa ../exec/exec.cmxa ../eval/eval.cmxa ../shell/shell.cmxa ../build/build.cmxa ../builtin/builtin.cmxa omake_shell.cmx omake_main.cmx ../clib/clib.a -cclib -lfam
** Cannot resolve symbols for ../clib/clib.a(lm_notify.o):
 _FAMErrno
 _FamErrlist
File "caml_startup", line 1:===============================    ] 01189 / 01242
Error: Error during linking
*** omake: 1189/1242 targets are up to date
*** omake: failed (4 min 47.47 sec, 234/234 scans, 357/519 rules, 801/2398 digests)
*** omake: targets were not rebuilt because of errors:
   src/main/omake.opt

DLL特有の名前修飾が原因と思われるものの、関数のシンボルは正常に解決できているようで、詳しい原因は分からなかった。GaminのDLLをGNU ldのruntime pseudo relocations機能を使ってビルドすれば良いのかもしれないが、そちらのビルドにも行き詰まったので、今回は手を引くことにした。

次に、警告が原因でビルドに失敗する問題について。参考資料によると、OCaml 3.12で厳し目に警告を出すようになったことが原因らしい。

コンパイラに渡すフラグをOMakefileで指定しているので、

--- ../omake-0.9.8.6-/OMakefile 2012-08-11 15:05:41.484375000 +0900
+++ OMakefile   2012-08-11 17:41:35.703125000 +0900
@@ -57,7 +57,7 @@
 #
 # OCaml options
 #
-OCAMLFLAGS[] += -w Ae$(if $(OCAML_ACCEPTS_Z_WARNING), z)
+OCAMLFLAGS[] += -w Aekr-29$(if $(OCAML_ACCEPTS_Z_WARNING), z)
 if $(THREADS_ENABLED)
     OCAMLFLAGS += -thread
     export

といった感じで、特定の警告は無視することにすれば良い。

以上。以下は参考資料。

2012-03-28

Chicken Schemeのビルド

WindowsでChicken Schemeをビルドするには、CygwinMinGWのどちらかのgccを使う。Visual Studioはサポートされていない。MinGWを使ってビルドする場合はMSYSは必須ではないけど、いずれにせよGNU makeは必要。以下の例はCygwinを使っている場合。

makeのマクロによって、どういった感じでビルドするか設定をする仕組み。どんなオプションがあるかはREADMEに書いてある。

% tar xf chicken-4.7.0
% cd chicken-4.7.0
% make PLATFORM=cygwin
% !! install

インストールする場所を指定しないと/usr/localにインストールされる。指定する場合はmake PLATFORM=cygwin PREFIX=/opt/chicken-4.7.0といった感じでPREFIXという名前のマクロを指定する。

2012-02-29

Gaucheと深い再帰

これは末尾再帰で木を走査で色々調べたときの副産物だけど、Gauche ユーザリファレンス3.6.2 パフォーマンスに関するヒント曰く、

深い再帰: Gauche の仮想機械(VM)は効率的なローカルフレーム割り当てのためにスタックを使っています。再帰が深くなって(プログラムにもよりますが、大体数百回から千回) スタックがオーバーフローするとスタックの内容をヒープに退避するというオーバーヘッドが生じます。 あるデータ量を越えたところでパフォーマンスの低下が見られたならば、深い再帰がないか調べてみて下さい。

とのこと。マジですかと思って、件のエントリで使った非末尾再帰のcopy-treeに10^6の要素を持つリストを渡してみたら、スタックがあふれることなく普通に動いた。これは便利。

末尾再帰で木を走査

何年か前、Scheme:末尾再帰で木をトラバースを読んだときは何をしてるかさっぱりだったんだけど、今なら分かるんじゃないかなー、と思って暇な時間に考えてたら自分でも書けたので、結果に至る過程を記録。

ちょっとアレンジして、木のコピーを例に考える。まずは、普通の再帰で素直に書いてみる。

;; 素朴な木のコピー
(define (copy-tree tree)
  (let loop ((node tree))
    (if (pair? node)
        (cons (loop (car node)) (loop (cdr node)))
        node)))

単純で分かりやすい。これをベースに、継続渡しスタイルを利用して末尾再帰のコードに変換してみる。人力CPS変換。

ちなみに、継続渡しスタイルについての詳しい話や、継続渡しスタイルと末尾再帰(末尾呼び出し)の関係についての説明は、ここではしない。他人に色々説明できるほど深く理解をしているわけじゃないので。詳しく知りたい人は、先人の書いた良い文章があると思うから、そちらを読んで欲しい。例えばThe 90 minute Scheme to C compilerとか。

とりあえず、最初に最終的なコードから。

;; 継続渡しスタイルによる末尾再帰な木のコピー
(define (copy-tree tree)
  (let loop ((node tree)
             (cont values))
    (if (pair? node)
        (loop (car node)
              (lambda (x)
                (loop (cdr node)
                      (lambda (y)
                        (cont (cons x y))))))
        (cont node))))

再帰に馴染みがないと、見た瞬間に心理的に3メートルくらい引くと思う。今あらためて見たら、書いた自分でも1メートルくらい引いた。だけど、段階を踏めばそんなに意味不明でもないので、あまり心配しなくても良い。

最初の素直な再帰の例に戻って、

;; 素朴な木のコピー
(define (copy-tree tree)
  (let loop ((node tree))
    (if (pair? node)
        (cons (loop (car node)) (loop (cdr node)))
        node)))

まず、継続を渡していくための引数が必要になる。ここでは、contという引数を増やす。なお、継続に定番のkとかいう名前を代わりに付けると、そっち方面への馴染みが薄い人への攻撃力をさらに強化できる。継続の中身はまだ考えない。

;; 継続渡しのために引数を増やす
(define (copy-tree tree)
  (let loop ((node tree)
             (cont ))
    (if (pair? node)
        (cons (loop (car node)) (loop (cdr node)))
        node)))

次に、最初にcontに指定する継続を考える。つまり、コピーし終わった木を受け取る、一番最後に実行される継続。copy-treeはコピーした木を返す手続きにしたいので、受け取った値をそのまま返す継続が良い。(lambda (x) x)とかでも良いけど、出来合いで、より省タイプなvaluesを使う。最近はCommon Lispに染まっていたのでidentityを探したが、なかった。

;; 最初に指定する継続はvalues
(define (copy-tree tree)
  (let loop ((node tree)
             (cont values))
    (if (pair? node)
        (cons (loop (car node)) (loop (cdr node)))
        node)))

継続渡しスタイルでは、値を返す代わりに、値を渡して継続を呼ぶので、値を返す部分はすべて継続の呼び出しになる。この例では二ヶ所。

;; 値を返す部分で継続を呼ぶ
(define (copy-tree tree)
  (let loop ((node tree)
             (cont values))
    (if (pair? node)
        (cont (cons (loop (car node)) (loop (cdr node))))
        (cont node))))

そして本題。loopの再帰呼び出しのときに指定する継続について考える。まずは(loop (car node))から。

(loop (car node))の継続、つまり、(loop (car node))が返す値をどうしたいのかという話だけど、ご覧の通り、(loop (cdr node))が返す値とペアを作りたい。そして、それを継続contに渡したい。コードで表現するとこうなる。

;; (loop (car node))の継続
(lambda (x)
  (cont (cons x (loop (cdr node)))))

継続が分かったので、実際のコードに当てはめてみる。

;; (loop (car node))を継続渡しスタイルに変更
(define (copy-tree tree)
  (let loop ((node tree)
             (cont values))
    (if (pair? node)
        (loop (car node)
              (lambda (x)
                (cont (cons x (loop (cdr node))))))
        (cont node))))

同じように、次は(loop (cdr node))の継続についても考えてみる。xとペアを作り、contに渡したいので、

;; (loop (cdr node))の継続
(lambda (y)
  (cont (cons x y)))

となる。これを実際のコードに当てはめると、

;; (loop (cdr node))を継続渡しスタイルに変更
(define (copy-tree tree)
  (let loop ((node tree)
             (cont values))
    (if (pair? node)
        (loop (car node)
              (lambda (x)
                (loop (cdr node)
                      (lambda (y)
                        (cont (cons x y))))))
        (cont node))))

最初に紹介したコードと同じになった。ご覧の通り、二ヶ所ある再帰は両方とも末尾呼び出しになっている。

実際に試してみると、

gosh> (let* ((t1 '(0 1 2))
             (t2 (copy-tree t1)))
        (values (eq? t1 t2) (equal? t1 t2) t2))
#f
#t
(0 1 2)
gosh> (let* ((t1 '(0 (1 2)))
             (t2 (copy-tree t1)))
        (values (eq? t1 t2) (equal? t1 t2) t2))
#f
#t
(0 (1 2))
gosh> (let* ((t1 '(0 ((1) 2) 3))
             (t2 (copy-tree t1)))
        (values (eq? t1 t2) (equal? t1 t2) t2))
#f
#t
(0 ((1) 2) 3)
gosh> 

意図した通りの動作をしてる模様。めでたし。

以下おまけ。Common Lispに(中略)ので、constantlyを探したがなかったため、(lambda _ 1)

;; 共通のwalkerを定義
(define (tree-walk tree leaf-proc inner-proc)
  (let loop ((node tree)
             (cont values))
    (if (pair? node)
        (loop (car node)
              (lambda (x)
                (loop (cdr node)
                      (lambda (y)
                        (cont (inner-proc x y))))))
        (cont (leaf-proc node)))))

;; 木のコピー
(define (copy-tree tree)
  (tree-walk tree values cons))

;; 葉を数える
(define (count-leaf tree)
  (tree-walk tree (lambda _ 1) +))

;; 葉を文字列に変換した木を作る
(define (copy-tree/string-node tree)
  (tree-walk tree
             (lambda (x)
               (if (null? x) x (x->string x)))
             cons))

関数型便利。

2011-11-28

LevelDBの共有ライブラリを作る

2011年11月28日現在、LevelDBのMakefileはまだ共有ライブラリの生成に対応してないので、それについてのworkaround。何度も同じことを考えるのを防ぐための備忘用。

まず、元になるオブジェクトコードを作る。

CFLAGS="-I$HOME/opt/snappy-1.0.4/include" \
gmake CC="g++ -m64" OPT="-O2 -DNDEBUG -fPIC" \
SNAPPY_CFLAGS="-I$HOME/opt/snappy-1.0.4/include -DSNAPPY"          

各指定の簡単な解説。

最初のCFLAGSはSnappyのヘッダの位置をプリプロセッサに伝える。標準の位置にあるならこの指定は不要。build_detect_platformでのみ有効で、MakefileでCFLAGSは上書きされる。SNAPPY_CFLAGSでも同じ指定をしているのはそのため。

今回は64ビットのライブラリを作りたい(Clozure CLの64ビットバイナリからFFI経由で使いたくなった)ので、CCはg++ -m64で。GCC 4.7より前のバージョンでは、GCCをビルドするときに設定しない限り、Solaris/x86-64でもデフォルトで64ビットコードを作らない。Solaris 11の標準のGCC 4は4.5.2。

効率的に動作して欲しいので、-fPICを付ける。適当な場所が見当たらなかったので、OPTに追加。

SNAPPY_CFLAGSは上に書いているように、ヘッダの位置の指定を追加。

次に、共有ライブラリを作る。

g++ -m64 -shared -Wl,-h,libleveldb.so.0 \
db/*.o port/*.o table/*.o util/*.o \
-Wl,-R,$HOME/opt/lib -L$HOME/opt/snappy-1.0.4/lib \
-lsnappy -lpthread -lrt -o libleveldb.so.0.0

64ビットのライブラリを作るため、-m64を指定するのは同じ。

-sharedで共有ライブラリを作ると指定し、-Wl,-hでsonameを指定するためのオプションをリンカに渡す。Solarisのldでは-sonameではなく-h。あるいは--soname。

材料のオブジェクトファイルは上の例のように指定すればすべて揃う。不安ならmakeしたときにarに渡されているファイルと照らし合わせれば確認できる。

リンクするライブラリは、Snappyと、環境に依存するライブラリ。これは、makeするときにできるbuild_config.mkを見れば分かる。PLATFORM_LDFLAGSに指定されているもの。Snappyも64ビットとしてビルドされていないと、当然リンクできないので注意。Snappyが標準の場所にないので、-Wl,-RでRUNPATHも指定する。

以上。あとは適当にサーチパスに放り込んで、libleveldb.so.0とlibleveldb.soという名前のシンボリックリンクをlibleveldb.so.0.0に張れば終わり。

2011-08-31

16進ダンプ

REPLから使いたくなって書いた。

使い方は以下の通り。(unsigned-byte 8)を要素に持つシーケンスを扱える。十行ごとにヘッダが入り、アドレス表示部分は長さ(標準で8桁)とオフセットを指定できる。テキスト表示部分はstandard characterかつgraphicな文字だけ表示できる。

> (hex-dump (sb-ext:string-to-octets "string"))
========== +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F =================
00000000 : 73 74 72 69 6E 67                               | string
NIL
>

2011-08-05

cl-fn

昨日のdefaliasだけど、似たようなコードを発見した。

Ron Garretユーティリティライブラリで、

(defmacro define-synonym (s1 s2)
  `(progn
     (defun ,s1 (&rest args) (declare (ignore args)))
     (setf (symbol-function ',s1) (function ,s2))))

という関数を定義してた。

何度か目を通してるはずなのに何故気付かなかったんだ、と一瞬後悔したけど、良く見たらs2を直接function特殊形式に渡してる。これだと、関数名lambda式以外を渡すことが許されないので、純粋に別名を付けるためだけを目的としたもののようだ。セーフ。こちらの主な目的は関数合成とかの結果に名前を付けることだから、上のコードは使えない。

安心した所で、無駄にならなかったdefaliasを、関数を扱うときに良く使われるユーティリティ関数とまとめてcl-fnというライブラリにした。ついでに、lambdaを書くのに疲れてきたので、前述のRon Garretのユーティリティからfnというマクロも貰ってきた。

;; &restを使わずに引数全体を束縛できる
(funcall (fn args args) 0 1 2)
;=> (0 1 2)

;; _で引数を無視できる(警告が出ないように宣言も付く)
(funcall (fn (x _) x) 0 1)
;=> 0

なんてことができるちょっと賢いやつ。ただし、リーダには手を入れないので、

((fn (x) x) 0)

みたいなことはできない。直接lambda式を呼びたいことなんてあんまりないから問題ないとは思うけど。

ちなみに、使いたい機能だけ楽にインポートできるように、パッケージを細かく分けておいた。例えば、curryとrcurryだけ使いたい場合は、cl-fn.paパッケージだけ使えば大丈夫。全部入りが欲しいならcl-fnを使う。余分なものまでインポートしなくて良いので綺麗好きな人でも安心。