when-required マクロ
Emacs の設定を複数のマシンの共有するときに面倒なのが、require しているパッケージがあったりなかったりすること。例えば、slime を require している .emacs をマシン A とマシン B で共有しているとする。マシン A には slime があり、マシン B には slime がないと、マシン B では .emacs の読み込みに失敗してしまう。
これを軽減するひとつの策として、require を必ず成功させるという方法がある。require には 3 つまで引数を渡すことができて、第 3 引数に t を渡すと、require に失敗しても error を吐かないようになる。
(require 'slime-autoloads nil t) (slime-setup)
これを色んなところに書くのはあんまりなので、以下の require-safe 関数で実現する。
(defun require-safe (pkg) (let ((val (require pkg nil t))) (if val val (progn (warn (concat "failed to require: " (symbol-name pkg))) nil))))
こんな風に使う。
(require-safe 'navi2ch) (when (require-safe 'slime-autoloads) (slime-setup))
when と組み合わせることで、require が成功したときだけ何かやるってことが実現できる。この when と require-safe の組み合わせは頻発するので、マクロにしたのが when-required マクロ。
(defmacro when-required (pkg &rest body) `(when (require-safe ,pkg) ,@body))
when-required を使うと、先の slime-autoloads の部分は以下のようになる。
(when-required 'slime-autoloads (slime-setup))
このままだとインデントが妙なので、when-required のインデント設定を変えておく。
(put 'when-required 'lisp-indent-function 1)
これで、
(when-required 'slime-autoloads (slime-setup))
こうなる。
require に失敗したときには、*Warnings* にその旨を出力するだけ。起動時に毎回 *Warnings* バッファが立ち上がるのは、うざいと言えばうざいのだけど、初期化が中断して中途半端な状態で立ちあがるよりはいいかなと思う。
追記
コメントで、defmacro の中で (declare (indent 1)) すれば、インデントの設定はいらないという指摘を受けた。いい感じなのでありがたく使わせてもらいます。以下、修正版。
(defmacro when-required (pkg &rest body) (declare (indent 1)) `(when (require-safe ,pkg) ,@body)) ; これはもういらないので削除する ; (put 'when-required 'lisp-indent-function 1)
Java で Erlang 風プロセスを作ってみる
Erlang を使ったことはないから、Erlang 風かどうかは実際のところ分からないんだけど…。他の人が作ってるのを見た感じ、まぁ大体あってるんじゃないかなーという希望を持ちつつ。メッセージボックス付きのスレッドってな感じで作った。
import java.util.*; import java.util.concurrent.*; public class Process { private static final Map<Thread, Process> processes = new HashMap<Thread, Process>(); private static final Object QUIT = new Object(); public static final Object BYE = new Object(); private final Thread thread; private final List<Process> children; private final BlockingQueue<Object> messageBox; protected Process(Thread thread) { this.thread = thread; this.children = new ArrayList<Process>(); this.messageBox = new LinkedBlockingQueue<Object>(); } public static Process getCurrent() { Thread t = Thread.currentThread(); synchronized (processes) { Process p = processes.get(t); if (p == null) { p = new Process(t); processes.put(t, p); } return p; } } public static Process spawn(Runnable body) { Process p = new Process(new Thread(body) { @Override public void run() { super.run(); synchronized (processes) { processes.remove(this); } } }); Process current = getCurrent(); current.children.add(p); synchronized (processes) { processes.put(p.thread, p); } p.thread.start(); return p; } public static Object getMessage() { return getCurrent().messageBox.poll(); } public static Object takeMessage() throws InterruptedException { return getCurrent().messageBox.take(); } public static Object takeMessage(long timeout, TimeUnit unit) throws InterruptedException { return getCurrent().messageBox.poll(timeout, unit); } public void kill() { putMessage(QUIT); } public void killChildren() { for (Process c : children) { c.kill(); } } public void putMessage(Object message) { if (message == QUIT) { killChildren(); if (thread.isAlive()) { thread.interrupt(); } synchronized (processes) { processes.remove(thread); } } else { messageBox.offer(message); } } }
以下サンプル。素数の数を数えあげる処理を、上の機構を使って並列処理でやってみたもの。悲しいかな、シングルスレッド版のほうが速い。processes 周りで synchronized してるところが、だいぶ (鈍化に) 効いてるような気がしてる。
import java.util.*; public class ConcurrentPrime { private static boolean isPrime(int n) { if (n == 2) return true; if (n % 2 == 0) return false; int m = 3; while (m <= Math.sqrt(n)) { if (n % m == 0) return false; m += 2; } return true; } private static class Calc implements Runnable { final Process counter; Calc(Process counter) { this.counter = counter; } public void run() { try { while (true) { Object m = Process.takeMessage(); if (m == Process.BYE) { break; } int n = ((Integer) m).intValue(); if (isPrime(n)) { counter.putMessage(n); } } } catch (InterruptedException quit) { } finally { counter.putMessage(Process.BYE); } } } private static class Counter implements Runnable { private final Process parent; private final int procs; public Counter(int procs) { this.parent = Process.getCurrent(); this.procs = procs; } public void run() { try { int count = 0; int end = 0; while (end < procs) { Object m = Process.takeMessage(); if (m == Process.BYE) { ++end; } else { ++count; } } System.out.println("prime count = " + count); } catch (InterruptedException quit) { } finally { parent.putMessage(Process.BYE); } } } private static class Balancer implements Runnable { private final Process parent; private final int max; private final int procLimit; public Balancer(int max, int procLimit) { this.max = max; this.procLimit = procLimit; this.parent = Process.getCurrent(); } public void run() { Process counter = Process.spawn(new Counter(procLimit)); counter.putMessage(2); List<Process> calcs = new ArrayList<Process>(); for (int i = 0; i < procLimit; ++i) { calcs.add(Process.spawn(new Calc(counter))); } int step = max / procLimit; for (int i = 3; i < max; i += procLimit) { for (int j = i, k = i + procLimit; j < max && j < k; ++j) { Process calc = calcs.get(j - i); calc.putMessage(j); } } for (Process calc : calcs) { calc.putMessage(Process.BYE); } try { Process.takeMessage(); parent.putMessage(Process.BYE); } catch (InterruptedException quit) { } } } private static void bench(String name, Runnable r) { long start = System.nanoTime(); r.run(); long end = System.nanoTime(); System.out.printf("%s: %.2f ms%n", name, (end - start) / 1000000.0); } public static void main(String[] args) { int n = -1; int m = -1; if (0 < args.length) { try { n = Integer.parseInt(args[0]); } catch (NumberFormatException e) { } if (1 < args.length) { try { m = Integer.parseInt(args[1]); } catch (NumberFormatException e) { } } } final int max = n > 0 ? n : 500000; final int procs = m > 0 ? m : 5; bench("concurrent " + max + " with " + procs, new Runnable() { public void run() { Process.spawn(new Balancer(max, procs)); try { Process.takeMessage(); } catch (InterruptedException quit) { } } }); bench("sequencial " + max, new Runnable() { public void run() { int count = 0; for (int i = 2; i <= max; ++i) { if (isPrime(i)) ++count; } System.out.println("prime count = " + count); } }); } }
以下、実行結果。
$ java ConcurrentPrime 10000000 5 prime count = 664579 concurrent 10000000 with 5: 12244.45 ms prime count = 664579 sequencial 10000000: 9181.09 ms $ java ConcurrentPrime 10000000 8 prime count = 664579 concurrent 10000000 with 8: 12989.82 ms prime count = 664579 sequencial 10000000: 9128.46 ms $ java ConcurrentPrime 10000000 3 prime count = 664579 concurrent 10000000 with 3: 13655.63 ms prime count = 664579 sequencial 10000000: 9190.02 ms
やっぱり並列処理はむずかしい。Java では大人しく java.util.concurrent を使うのがよさげかなぁ。
追記:
一度投稿したあと、よくよく見てみると色々と間違ってたので修正した。
昨日のマクロ
謎が解けた。というか会社の人に教えてもらった。
(defmacro when* (test &rest body) (if test `(progn ,@body)))
こんな感じのが昨日のマクロ。
これの何が悪かったのかというと、test がフォームのまま評価されていたということ。つまり
(when* (equal 1 2) 1)
は
(if '(equal 1 2) (progn 1))
てな感じに展開される。'(equal 1 2) は非 nil なので、(progn 1) が結果として生成されるっていう感じかなぁ。
解決策はというと、eval しちゃえばいいというものだった。
(defmacro when* (test &rest body) (if (eval test) `(progn ,@body)))
eval って普段使ってる言語ではまず出てこないから、存在を忘れてた。
昨日のマクロその 2
これで何をやりたかったかというと、Emacs Lisp のコンパイル警告を抑止したかっただけ。無視しても大したことはないんだろうけども。
例えば howm をバイトコンパイルすると、以下のようなコンパイル警告が出る。
In riffle-summary-mode: riffle.el:191:4:Warning: `make-local-hook' is an obsolete function (as of Emacs 21.1); not necessary any more.
Emacs 21.1 より新しいバージョンじゃ、make-local-hook なんて使わなくていいよ的な警告。これを emacs-version を条件として、利用するかしないかを切り替えたい。普通の when だとただの条件分岐になって、結局警告が出る。
そういうわけで C の条件付きコンパイル的な、ある条件を満たす場合だけフォームを生成するマクロがほしかったのでした。
適当なところに when* を宣言して、
(when* (or (string-equal emacs-version "21.1") (string-lessp emacs-version "21.1")) (make-local-hook 'post-command-hook))
とすれば、さっきの警告は出なくなる。
マクロで条件つきコンパイルみたいなことがやりたい
C の #if みたいなことを、Lisp のマクロでやりたい。が、うまくいかず。xyzzy と Emacs で想定した動きにならなかったので、なんか自分の凡ミスだとは思ってるんだけど。
(defmacro when* (pred &rest body) (when pred `(progn ,@body)))
で、以下のようになってほしい。
(macroexpand '(when* (equal 1 1) 1)) (progn 1) (macroexpand '(when* (equal 1 2) 2)) nil
実際はこうなる。
(macroexpand '(when* (equal 1 1) 1)) (progn 1) (macroexpand '(when* (equal 1 2) 2)) (progn 2)