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

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

以上。以下は参考資料。