2007-10-05

.emacs (2)

MacOSXでAquaSKKを使っていると困るのがEmacsのキーバインドとの衝突。^C-jで日本語入力に変わるのですが、アプリケーションにメッセージが渡る前の処理なのでEmacsでキーが捕まえられません。でも他のアプリケーションでは^C-jでの切替は便利なので変えたくありません。

そんな場合に.emacsの設で、(global-set-key key cmd)を使って実行コマンドの割り当てを変更しても良いのですが、もっと根本的で簡単な方法があります。Emacsではkeyboard-translate-tableという変数があるので、これを弄ってやりましょう。

(keyboard-translate ?\C-m ?\C-j)
(keyboard-translate ?\C-j ?\C-m)

ついでにvi(m)バインドに慣れている人はDELも^C-hに割当ててしまいましょう。

(keyboard-translate ?\C-h ?\C-?)
(keyboard-translate ?\C-? ?\C-h)

これでvi(m)とEmacsを使い分ける際の違和感が一つなくなります。

2007-10-04

.emacs (1)

会社の作業環境をターミナル越しのemacsからMeadow 2.10に移行しました。

Windows端末は検証もありますから極力弄らないのが仕事の流儀、だからEmacs lisp内で完結してしまっている環境は大変便利なわけです(本当はEclipseがちっとも好きになれないだけ)。ところでMeadow 2.XXはNetinstallerがあるので拡張の追加が楽でした。SKKを使う人はapelも入れる事をお忘れなく。

現状の.emacsはこんな感じ(memo)です。SKK + Common Lispというありがちな設定の間にPrologなんかを挟んでるのが僕の特徴です(どんだけー)。こういった古い言語の設定もちゃんと今に生きている辺りはEmacsの素晴らしさですねー(?)。

;; Fundamentals
(global-font-lock-mode t)
(show-paren-mode t)
(setq transient-mark-mode t)

;; SKK
(autoload 'skk-mode "skk" nil t)
(global-set-key "\C-x\C-j" 'skk-mode)
(global-set-key "\C-xj" 'skk-auto-fill-mode)
(global-set-key "\C-xt" 'skk-tutorial)
(add-hook 'isearch-mode-hook
(function (lambda ()
(and (boundp 'skk-mode) skk-mode
(skk-isearch-mode-setup)))))
(add-hook 'isearch-mode-end-hook
(function (lambda ()
(and (boundp 'skk-mode) skk-mode
(skk-isearch-mode-cleanup)
(skk-set-cursor-color-properly)))))
(setq skk-large-jisyo "C:/Meadow/packages/etc/skk/SKK-JISYO.L")
(setq skk-tut-file "C:/Meadow/packages/etc/skk/SKK.tut")

;; SWI-Prolog - http://www.swi-prolog.org/ -
(setq auto-mode-alist
(append '(("\\.pl" . prolog-mode))
auto-mode-alist))
(setq prolog-program-name "C:/Program Files/pl/bin/plcon")
(setq prolog-consult-string "[user].\n")

;; CLISP - http://clisp.cons.org/ -
(setq inferior-lisp-program "C:/clisp/clisp")

;; SLIME - http://common-lisp.net/project/slime/ -
(add-to-list 'load-path "C:/Meadow/slime-2.0/")
(require 'slime)
(slime-setup)
(setq slime-net-coding-system 'euc-jp-unix)
(setq common-lisp-hyperspec-root "c:/clisp/doc/HyperSpec/")
(add-hook 'inferior-lisp-mode
(lambda () (inferior-slime-mode t)))
(add-hook 'lisp-mode-hook
(lambda ()
(slime-mode t)
(show-paren-mode)))
(slime-autodoc-mode)


ただもっぱら納品仕事はc++なのでvi(m)で書いています。
c/c++は構文がそもそも行指向なところがありますから、Emacsによるメリットが薄いというのが個人的な印象です。特に対話実行など一切無い世界ですし、<CTRL>や<META>を組み合わせた操作が入らない分、タイピング総数はvi(m)での編集の方が圧倒的に楽かも、と思っています(だったらEclipse使えよという見方もあるけど)。

Prolog (1)

Prologの強みはなんと言ってもパターンマッチングとバックトラックです。それを利用することによって、「どうやって(how)?」ではなく「どんな(what)?」でプログラミングを行うことが可能となります。またPrologは型の無い動的な言語なので、自分がどういった仕様を欲しているのか、スケッチするように考えながら作ることが出来ます。例えば簡単な電卓を作るプロセスを紹介します。

まずは四則演算がどういったものなのか、思いついたまま書き下してみました。

% calc_op/4
calc_op(X, +, Y, Z) :- Z is X + Y.
calc_op(X, -, Y, Z) :- Z is X - Y.
calc_op(X, *, Y, Z) :- Z is X * Y.
calc_op(X, /, Y, Z) :- Z is X / Y.

引数の部分がこれで良いのかどうか、現時点では分かりません。なので他のアイデアとして、こんな事も考えてみました。

calc_op([X, +, Y | R], Ans) :-
calc_op(R1, Ans),
append([Z], R, R1),
Z is X + Y.

ですが、この方法だと演算子の優先度を解決するために、演算子の組み合わせの数だけパターンを用意してやらなければならない事が分かります。だって、+Yの後に*が来たら、Zはまだ計算しちゃいけませんからね。もちろん、後者の方法でも場合分けのパターンを網羅してやれば(MECE... MECE... MECE...)、誤った選択はバックトラックによって解の候補から消えていくので間違った実装では無いでしょう。でも組み合わせの数は簡単に膨れ上がることは察してやる必要があります。

ここではとりあえず、演算子が増える事を想定して、演算操作と操作の組み合わせは別の論理として表現する道を選びました。それでは、前者の個別に表現された演算の論理を制御する部分を考えます。

% calc/2
calc([], []).
calc([X], [X]).
calc([X, Y, Z | R], Ans) :-
calc_op(X, Y, Z, A1),
append([A1], R, E),
calc(E, Ans).

一歩づつ、とにかく実行させて行くことが重要です。クロッキー片手に製図のような線を引くことはないですよね?とりあえず数式をリストで渡してやれば、頭から演算を適用して答えを返すところまで来ました。当然、数式によっては間違います。

ここまではほとんど何も考えずに来るわけですが、ここで少し考えます。さて演算子の優先順位をどうやって解決してやろうか?と。僕も含め、恐らく多くの人はまず入力されたリストをパースしてRPNのような構造に変換しようと考えると思います。今回はそれだと普通過ぎるなと思ったのでわざと違うことをしてみました。実際に目の前に式を提示されたらどうやって順番決めて解いていくかしら?というアプローチです。

ここは極端な話、人によりけりですし、問題によって解き易いように分解したりファクタリングしたりすると思います。ですが単調な方法で行けば、まず一番優先度の高いところを解決して項を書き換えて、新しい式に次の優先度の書換えを施して、という手順です。

1+2*3-4/2 => 1+6-4/2 => 1+6-2 => 7-2 => 5

左から右、乗除から加算。つまり数式に対してステージングを行っているわけですね。そこで2つのステージに分けてみます。

% calc/2
calc(Exp, Ans) :- calc1(Exp, Tmp), calc2(Tmp, Ans).

% calc1/2
calc1([], []).
calc1([X], [X]).
calc1([X, Y, Z | R], Buf) :-
calc1op(X, Y, Z, A1, B1),
append(A1, R, E),
calc1(E, A2),
append(B1, A2, Buf).

% calc2/2
calc2([], []).
calc2([X], [X]).
calc2([X, Y, Z | R], A2) :-
calc2op(X, Y, Z, A1),
append(A1, R, E),
calc2(E, A2).

どちらのステージでもリストの先頭から順にマッチングしたものを演算論理に渡します(ちなみにこれって、C/C++で言うディスパッチャみたいな処理ですね。パターンマッチのおかげで多重ディスパッチも簡単です)。演算論理の結果と先頭を挿げ替えて、再帰的に、ただしステージごとに分離した形で、マッチングを行っていきます。

1つめのステージ(calc1)では乗除算だけが実行されて、項が書き換わった式を生成します。2つめのステージ(calc2)では渡された加減算のみの式に演算を適用して答えを求めます。ステージ別に演算に要求される仕様が変わりましたから、このままでは動きません。最初の演算ルールを修正しましょう。

% calc1op/5
calc1op(X, *, Y, [Z], []) :- Z is X * Y.
calc1op(X, /, Y, [Z], []) :- Z is X / Y.
calc1op(X, +, Y, [Y], [X, +]).
calc1op(X, -, Y, [Y], [X, -]).

% calc2op/4
calc2op(X, +, Y, [Z]) :- Z is X + Y.
calc2op(X, -, Y, [Z]) :- Z is X - Y.

1つめのステージでは+,-演算を書き換えず、またバックトラックを起こさないために若干不恰好な論理を加えています。これで一先ずは四則演算が行えるようになりました。

?- calc([2,*,4,-,3,*,1], Ans).
Ans = 5 ;
No

正しい答えがただ1つ見つかりました。