2013年2月1日金曜日

emacs24 elisp 新機能

M-x view-emacs-news をざっと眺めてみて、特に lisp 関連で気になった事柄、 のメモ

レキシカルスコープ

emacs24からは、lexical-binding というファイルローカルな変数に t をセットすれば、レキシカルスコープ環境で、 lispプログラムを評価してくれるらしい。
つまり、ソースファイルの先頭に、
;; -*- coding: utf-8; lexical-binding: t -*-
とか書いとけば、common-lisp や schemeのように、普通にクロージャーが書けるということだ。
試してみた。
$ cat lextest.el
;; -*- coding: utf-8; lexical-binding: t -*-
;; カウンター生成関数
(defun make-counter (&optional init)
  (let ((count (or init 0)))
    #'(lambda (&optional com) 
        (case com
          ((peek :peek)  count)
          ((reset :reset) (setq count (or init 0)) count)
          (t (setq count (1+ count)) count)))))
$
この、lextest.el を M-x load-file で読み込み、 *scratch* バッファーで動作確認。
(setq my-counter (make-counter 100))
 => (closure ...)
(funcall my-counter)
 => 101
(funcall my-counter)
 => 102
(funcall my-counter :peek)
 => 102
(funcall my-counter)
 => 103
(funcall my-counter :reset)
 => 100
(funcall my-counter)
 => 101
いちいち、funcall で 関数シンボルを呼び出す以外は、 scheme と同じように書けるのが嬉しい。
では、ソースファイル中の、 ある一部の関数だけレキシカルバインディングしたい時はどうするのか?
安易に、
(let ((lexical-binding t)) ..... )
とやっても駄目だった。
emacs24からは、eval が拡張されており、`M-x describe-function` では、
    (eval FORM &optional LEXICAL)
        Evaluate FORM and return its value.
        If LEXICAL is t, evaluate using lexical scoping.
とある。第2引数に t を渡してやればレキシカルスコープでの評価になるらしい。
先の make-counter を、この eval を使って定義してみると、
(eval
 '(defun make-counter (&optional init)
    (let ((count (or init 0)))
      #'(lambda (&optional com) 
          (case com
            ((peek :peek) count)
            ((reset :reset) (setq count (or init 0)) count)
            (t (setq count (1+ count)) count)))))
 t)
何か変なコードだけど、一応これでちゃんと動作した。
上のように書けば、従来のダイナミックスコープなソースコードの中に、 部分的にレキシカルスコープなコードを割りこませることもできそうだ。でも、 他にもっといい方法がありそうなもんだが?

letrec マクロ

たぶん、レキシカルスコープ導入の関係で追加されたのだと思う。
よくある相互参照再帰のパターンで確認してみた。
;; -*- lexical-binding: t -*-
(defun foo (n)
  (letrec ((even-p #'(lambda (x)
                       (or (= x 0) (funcall odd-p (- x 1)))))
           (odd-p  #'(lambda (x)
                       (and (not (= x 0)) (funcall even-p (- x 1)))))
           )
    (cond ((funcall even-p n) "EVEN")
          ((funcall odd-p  n) "ODD")
          (t "????"))))
(mapcar 'foo '(100 101 102 103)) 
=> ("EVEN" "ODD" "EVEN" "ODD")
上のコードは、スコープに関係なく動作する。 でも、letrec の部分をletに置き換えてみると、 ダイナミックスコープな環境では動作してしまうが、 lexical-binding な環境ではエラーになる。
実はこのあたり、ダイナミックスコープでも letrec みたいにしないと駄目なんじゃないか?と僕はずっと思っていた。 だから、今までは、
 (let ((even-p nil) (odd-p nil))
   (setq even-p #'(lambda ....  ))
   (setq odd-p #'(lambda ....  ))
   .....)
てな感じの汚いコードを書いていた。このあたりの理解がまだまだあやふやだ....

pcase マクロ

emacs24から導入された、ML形式のパターンマッチング構文らしいのだが、 MLって知らないし、`M-x describe-function pcase` を 読んでもよくわからない。
で、ググってみた。
ここ に、pcase を使ったサンプルがたくさん書いてある。
このサンプルと、`describe-function pcase` のドキュメントとを両方眺めると、なんだか少しだけわかったような気になる。
要は、
(pcase EXP
  (UPATTERN1 CODE1 ...) 
  (UPATTERN2 CODE2 ...) 
  ...)
のフォームをとり、EXPに対して UPATTERNのパターンマッチングをかけ、 照合すれば 右辺の CODE... を実行する。

そして、(UPATTERN CODE ...) の所に (SYM code...) と書くと、 何でもマッチし、かつ、SYMはEXPに束縛され code... 部で使うことができる。

SYMにバッククオートを付けて、(`SYM code..) と書くと、 (eq SYM EXP) でないと照合しない。

リストに対しては、 (`(,v . ,rest) code ... ) と書けば、EXP の car部が v に、 cdr部が rest に束縛され、それらを code... 部で使うことができる。

という感じかな? 細かい所は依然として不明だけど。

試しに、pcase を使って、string-join みたいなものを作ってみた。
(defun string-join (lst &optional term)
  (pcase lst
    (`nil "")
    (`(,e . nil) e)
    (`(,e . ,rest) (concat e (or term ",") (string-join rest term)))
    ))
(string-join '()) ;=> ""
(string-join '("a")) ;=> "a"
(string-join '("a" "b")) ;=> "a,b"
(string-join '("a" "b" "c")) ;=> "a,b,c"
(string-join '("a" "b" "c" "d") "|") ;=> "a|b|c|d"
なんか、scheme の syntax-rules の雰囲気?
まだ漠然としかわかっていないのだが、ちゃんと理解すれば、 パーサを書く時とかマクロを書く時などに重宝しそうな気がする。

notifications.el

emacs24 の NEWSの説明には、
** notifications.el provides an implementation of the Desktop
Notifications API. It requires D-Bus for communication.
とある。
要は、mac の growlnotify のように、 簡単なメッセージをデスクトップ上に表示できる機能らしい。 但し、D-Bus が動いてないといけない。

試しに、ubuntuの emacs24.1で、
  (require 'notifications)
  (notifications-notify :title "Notifications Test" :body  "Hello!")
を実行させた結果。

関数、notifications-notify は notification id を返すのでそれを覚えておき、 再度その id で notifications-notify を実行させれば、同じ位置でメッセージを表示させることもできるみたいだ。 例えばこれ、
(require 'notifications)
(setq my-notification-id nil)
(defun my-notify (message)
  (interactive "sMessage: ")
  (setq my-notification-id
        (notifications-notify :title "Notifications-Notify"
                              :body message
                              :replaces-id my-notification-id
                              :timeout (* 10 1000))))
M-x my-notify で表示させた通知画面は、そのタイムアウトである10秒内に 再度実行させても同じ位置で表示される。

2010年5月7日金曜日

VA_ARGS マクロ

objective-c の NSLog について、デバッグ時のみログを出力し、 リリースビルドではおとなしくさせる方法を知りたく、ググってみた。
いろいろなサイトで紹介されており、 次のようなマクロを定義すれば出来るとある。

    #ifdef MYDEBUG 
    #define MYLOG(...) NSLog(__VA_ARGS__)
    #else
    #define MYLOG(...) 
    #endif
で、この、__VA_ARGS__ って一体何だ? と思い、本家 gccの ドキュメント を探ってみると、 にずばりそのものの記述があった。
どうやら、printf みたいなことをマクロでやるためのものらしい。
C言語なんて もう何十年も前に K&R を読んだっきり、 全く勉強していなかったけど、いつのまにか進歩している!
デバッグする上では、この MYLOG マクロで充分なんだけど、どうせなら 、 NSLog を書いたクラスやメソッドの場所がわかればいいなあと思い、さらに gccのページを探ってみると.....
みつけた!
__PRETTY_FUNCTION__ という変数が、それを使った場所のメソッド名や関数名を、 文字列として保持しているらしい。
じゃあ、ということで、メソッド名も出力してくれる、MYLOG2を書いてみた。

#define MYLOG2(fmt, ...) \
  NSLog([@"%s " stringByAppendingString:(fmt)], \
        __PRETTY_FUNCTION__, ##__VA_ARGS__)

__VA_ARGS__ の前の ## は、 VA_ARGS が空の時に、 ',' の展開をさせないためのおまじないだ。

以下が、そのサンプルと実行結果。
# サンプルソース
$ cat test-mylog.m
// -*-mode:objc; coding:utf-8;  -*-
#import <Foundation/Foundation.h>

#define MYLOG2(fmt, ...) \
  NSLog([@"%s " stringByAppendingString:(fmt)], \
        __PRETTY_FUNCTION__, ##__VA_ARGS__)

@interface MyTest : NSObject {
  NSString *message; 
}
@property (readwrite, copy) NSString *message;
@end

@implementation MyTest
- (id) init {
  self = [super init];
  if(self){message = nil;}
  return self;
}
- (void) dealloc {
  MYLOG2(@"dealloc!"); // Test for Empty VA_ARGS
  [self setMessage:nil];
  [super dealloc];
}
-(NSString *)message{ return message;}
-(void)setMessage:(NSString *)m {
  NSString *fmt = @"message will change from %@ to %@.";
  if( ! m )fmt = @"message '%@' will release.";
  MYLOG2(fmt, message, m);
  m = [m copy]; [message release]; message = m;
  MYLOG2(@"message did change to %@", message);
}
@end

int main(int argc, char *argv[]){
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

  MyTest *test = [[[MyTest alloc] init] autorelease];
  [test setMessage:@"Hello"];

  [pool release];
  return 0;
}

# コンパイル
$ gcc test-mylog.m -framework Foundation 

# 実行
$ ./a.out
 -[MyTest setMessage:] message will change from (null) to Hello.
 -[MyTest setMessage:] message did change to Hello
 -[MyTest dealloc] dealloc!
 -[MyTest setMessage:] message 'Hello' will release.
 -[MyTest setMessage:] message did change to (null)
$
ちゃんと、[ クラス名 セレクター ] が表示される。

調子に乗って、MYLOG2と同じやり方で、alert の簡易マクロを書いてみた。

#define MYALERT_OK(title, ...) \
  ([[NSAlert alertWithMessageText:(title) \
                    defaultButton:@"OK"  \
                  alternateButton:nil otherButton:nil \
        informativeTextWithFormat:__VA_ARGS__ ] runModal])

#define MYALERT_CANCEL_OK(title, ...) \
  ([[NSAlert alertWithMessageText:(title) \
                    defaultButton:@"CANCEL" \
                  alternateButton:@"OK" otherButton:nil \
        informativeTextWithFormat:__VA_ARGS__ ] runModal])

このマクロは、こんな感じで使える。
if( ! [[NSFileManager defaultManager] fileExistsAtPath:fileName] ){
  int choice = 
    MYALERT_CANCEL_OK(@"Confirm", @"Create new file:%@ ?",fileName);
  if(choice == NSAlertDefaultReturn){
    MYALERT_OK(@"CANCELED!",@"");
    ..................;
  }else{
    ..................;
  }
}
これなら、javascript 並みにたやすく alert デバッグができそうだ。

ところが、この MYLOG2 と MYALERT_CANCEL_OK をいろいろと使っているうち に、うまくいかない場合を見つけてしまった。
例えば、
   MYLOG2([NSString stringWithFormat:@"%@ %%s",
         @"Program name is"], argv[0]);
と書くと、これは、
    error: syntax error before ‘)’ token
    error: syntax error before ‘]’ token
のコンパイルエラーになってしまう。
でも、最初の引数を、次のように括弧で囲むとエラーは無くなる。
  MYLOG2( ([NSString stringWithFormat:@"%@ %%s",
        @"Program name is"]), argv[0]);

どうやら、マクロの実引数に、stringWithFormat: のような VA_ARGS形式のセレクターを使うと、そのままでは、 ',' が変な風に解釈されてしまい、 コンパイルエラーになってしまうみたいだ。 でも、その実引数が、仮引数の ... 部分にマッチする場合は何の問題もない。
とりあえずの解決策は、( ) で囲め、ということか。
このあたりはさっぱりわからない。でも、マクロは楽しい。

マクロを使って、こんなことも出来てしまった。
$cat test-indexset.m
// -*-mode:objc; coding:utf-8;  -*-
#import <Foundation/Foundation.h>

/*
 * Ruby の NSIndexSet.each{|val| ...} 相当のつもり。
 * VA_ARGS を使っているが、実際には、3引数のマクロとして使う
*/
#define MYEACH_INDEXSET(idx,idxvalue, ...) \
{ \
  NSIndexSet *___tmpidx___ = (idx); \
  NSUInteger idxvalue = [___tmpidx___ firstIndex]; \
  while( idxvalue != NSNotFound ){ \
    __VA_ARGS__ \
    idxvalue = [___tmpidx___ indexGreaterThanIndex:idxvalue]; \
  } \
}

int main(int argc, char *argv[]){
  NSAutoreleasePool *pool;
  pool = [[NSAutoreleasePool alloc] init];
  
  NSArray *strList =
    [NSArray arrayWithObjects:@"zero",@"one",@"two"
             ,@"three",@"four",@"five", nil];
  NSMutableIndexSet *idxSet =
    [NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(0,4)];
  [idxSet addIndex:10];[idxSet addIndex:5];
  MYEACH_INDEXSET(idxSet, val,
                  {
                    NSString *s = @"?";
                    if( val < [strList count]){
                      s = [strList objectAtIndex:val];
                    }
                    NSLog(@"strList[%d]=%@",val,s);
                  });
  [pool release];
  return 0;
}

$ gcc  test-indexset.m -framework Foundation

$ ./a.out
   strList[0]=zero
   strList[1]=one
   strList[2]=two
   strList[3]=three
   strList[5]=five
   strList[10]=?
$
僕としては、ruby のイテレータもどきを、 objective-c で実装したつもりだ。
マクロの実引数に { } で囲んだ文を指定して、さらに、展開も文なんだけど、 とりあえずサンプルは動作している。
でも、こんなの、たぶん問題だらけなんだろうなあ....
確か、昔読んだCの落とし穴本に、 「マクロは文ではない」と書いてあったと思う。

2010年2月22日月曜日

cocoa emacs のインストール(その2)

インラインパッチ が新しくなっていたので、cocoa emacs を再インストールした。

インストール

インストールは、 MacEmacs JP Project サイトにある、
「IMEパッチ適用 Emacs 23.1.92 では...」
の所に書いてあるコマンドを、そのまま実行するだけで完了。

.emacs.el も、 今までと同じ内容 で問題なく動作した。

使ってみて

漢字変換中に C-iC-o を使った時のカーソル変化が、 前のバージョンよりもわかりやすくなり、日本語の入力がとても使いやすくなった。

ただ、do-applescript がおかしな動作をする。
僕の環境が悪いのかもしれないが、
(do-applescript "display dialog \"Hello\"")
を実行すると、何故かemacsが、
Fatal error (10) Emacs[3125:10b] 
   *** CFMessagePort: bootstrap_register(): failed 1100 (0x44c) 
   'Permission denied', port = 0x3803, 
    name = 'org.gnu.Emacs.ServiceProvider'
Abort trap
というエラーメッセージを吐いて、落ちてしまうのだ。

この現象が起きるのは、default-input-method が MacOSX の場合に限るようなので、とりあえずの回避策として、 do-applescriptを、以下の用に再定義してみた。
(when (string-match "23\\.1\\.92" emacs-version)
  (defun do-applescript (script)
    (let ((default-input-method "japanese"))
      (ns-do-applescript script))))
ns-do-applescriptが動く時だけ、 input-method を japanese に変更しただけだが、 これでなんとか動くようになる。
もともと、do-applescript は、 ns-do-applescript のエイリアスなので、再定義しても大丈夫だろう。

2009年11月4日水曜日

cocoa emacs のインストール

leopardに、cocoa emacs (emacs23.1) をインストールした時のメモ。

INSTALL

インストールそのものは、 MacEmacs JP Project の 「IMEパッチの適用」に書いてあるコマンドを、そのまま実行すればうまくいく。
$ tar xvfz inline_patch-20090617.tar.gz
$ tar xvfz emacs-23.1.tar.gz
$ cd emacs-23.1
$ patch -p 0 < ../inline_patch-20090617/emacs-inline.patch
$ ./configure --with-ns --without-x
$ make bootstrap
$ make install
$ open nextstep/Emacs.app
ただし、これらの作業を、emacsのシェルモードで行うと、
 Error: charsets directory does not exist.
 ethiopic.el: Error: Failure in loading charset map: MULE-ethiopic
のエラーが出てコンパイルに失敗する。
たぶん、EMACS関連の環境変数が邪魔になっているのだろうと思い、 configure実行の前に、
$ unset EMACSAPP EMACSDATA EMACSPATH EMACSDOC INSIDE_EMACS EMACSLOADPATH
を実行すれば解決。もっとも、ターミナルで作業する分には問題ない。

フォント設定

フォントは、 ここ とか ここ を参考にして、意味もわからずに見様見真似で設定してみた。
こんな感じだ。
(create-fontset-from-fontset-spec
  "-*-*-medium-r-normal--14-*-*-*-*-*-fontset-hiramaru14" nil t)
(create-fontset-from-fontset-spec
  "-*-*-medium-r-normal--16-*-*-*-*-*-fontset-hiramaru16" nil t)
(create-fontset-from-fontset-spec
  "-*-*-medium-r-normal--20-*-*-*-*-*-fontset-hiramaru20" nil t)
(mapc
 #'(lambda (fontset)
     (set-fontset-font fontset 'japanese-jisx0208
                       '("Hiragino Maru Gothic Pro" . "iso10646-1"))
     (set-fontset-font fontset 'katakana-jisx0201
                       '("Hiragino Maru Gothic Pro" . "iso10646-1"))
     (set-fontset-font fontset 'japanese-jisx0212
                       '("Hiragino Maru Gothic Pro" . "iso10646-1"))
     ) (list "fontset-hiramaru20" "fontset-hiramaru16" "fontset-hiramaru14"))
(let (
      ;; (my-fontset "fontset-hiramaru14") ;; ちっちゃいフォント
      (my-fontset "fontset-hiramaru20") ;; でっかいフォント
     )
  (set-default-font my-fontset)
  (add-to-list 'default-frame-alist `(font . ,my-fontset)))
いちおう、これで、英数文字と日本語文字の幅が1対2になり、 carbon の時と同等にきれいに表示されるようになった。

IMの設定

.emacs.el に以下を追加すると、 かなキーでモードラインに「あU:」とかが表示されるようになる。
(set-language-environment "Japanese")
(setq default-input-method "MacOSX")
ただ、「ことえり」の場合は、 C-x o などでバッファーを切り替えた時に、 モードラインの表示が現在の変換モードと一致しなくなる。
そこで、コマンドループのフック( post-command-hook )に、 IMの更新をするコードを追加してみた。
(load "cl")
(add-hook
 'post-command-hook
 (lexical-let ((previous-buffer nil))
   #'(lambda ()
       (unless (eq (current-buffer) previous-buffer)
         ;; (message "Change IM %S -> %S" previous-buffer (current-buffer))
         (if (bufferp previous-buffer) (mac-handle-input-method-change))
         (setq previous-buffer (current-buffer))))))
無理矢理っぽいコードだが、とりあえずこれで、 バッファー切り替えでも違和感なく使えるようになった。

DocView

emacs の中でPDFがみられるという、 doc-view を試してみた。 でも、ディスク内のあらゆるPDFを試してもうまく表示されない。
emacs-23.1/lisp/doc-view.el をみてみると、Ghostscript と、 xpdf または teTeX が必要と書いてある。
さっそく port でインストール。
$sudo port install ghostscript
$sudo port install ghostscript-fonts-hiragino
$sudo port install xpdf
これでdoc-viewが動き、PDFが表示できるようになる。
でも、macでは、全てのpdfをdocviewで表示できるわけではないらしい。 見れるファイルもあるし見れないのもある。

.emacs.el

cocoa emacs用に、僕が使っている .emacs.el の抜粋。
いろいろなサイトに載っていた設定例の寄せ集めだが、 これでも、試行錯誤の連続で、落ち着くまでには結構苦労した。
(if (>= emacs-major-version 23)
    ;; cocoa emacsの設定
    (progn
      ;; Command キーをMetaキーにする
      (setq ns-command-modifier (quote meta))
      ;; (setq ns-alternate-modifier (quote super))
      (setq ns-alternate-modifier nil)

      ;; IM 設定
      (set-language-environment "Japanese")
      (setq default-input-method "MacOSX")
      (prefer-coding-system  'utf-8-unix)
      ;; minibufferは英数モードで始める
      (add-hook 'minibuffer-setup-hook 'mac-change-language-to-us)
      ;; buffer切り替えの時にも、IM状態をアップデート
      (load "cl")
      (add-hook
       'post-command-hook
       (lexical-let ((previous-buffer nil))
         #'(lambda ()
             (unless (eq (current-buffer) previous-buffer)
               ;; (message "Change IM %S -> %S" previous-buffer (current-buffer))
               (if (bufferp previous-buffer) (mac-handle-input-method-change))
               (setq previous-buffer (current-buffer))))))

      (define-key global-map [ns-drag-file] 'ns-find-file) ;; find-file

      ;; Set text scale key 
      (global-set-key "\C-c+" #'(lambda () (interactive) (text-scale-increase 1)))
      (global-set-key "\C-c-" #'(lambda () (interactive) (text-scale-decrease 1)))
      (global-set-key "\C-c0" #'(lambda () (interactive) (text-scale-increase 0)))

      ;; font 設定
      ;; (setq fixed-width-use-QuickDraw-for-ascii t)
      (setq mac-allow-anti-aliasing t)

      (create-fontset-from-fontset-spec
       "-*-*-medium-r-normal--14-*-*-*-*-*-fontset-hiramaru14" nil t)
      (create-fontset-from-fontset-spec
       "-*-*-medium-r-normal--16-*-*-*-*-*-fontset-hiramaru16" nil t)
      (create-fontset-from-fontset-spec
       "-*-*-medium-r-normal--20-*-*-*-*-*-fontset-hiramaru20" nil t)
      (mapc
       #'(lambda (fontset)
           (set-fontset-font fontset 'japanese-jisx0208
                             '("Hiragino Maru Gothic Pro" . "iso10646-1"))
           (set-fontset-font fontset 'katakana-jisx0201
                             '("Hiragino Maru Gothic Pro" . "iso10646-1"))
           (set-fontset-font fontset 'japanese-jisx0212
                             '("Hiragino Maru Gothic Pro" . "iso10646-1"))
           ) (list "fontset-hiramaru20" "fontset-hiramaru16" "fontset-hiramaru14"))
      
      (create-fontset-from-fontset-spec
       "-*-*-medium-r-normal--14-*-*-*-*-*-fontset-hirakaku14" nil t)
      (create-fontset-from-fontset-spec
       "-*-*-medium-r-normal--16-*-*-*-*-*-fontset-hirakaku16" nil t)
      (create-fontset-from-fontset-spec
       "-*-*-medium-r-normal--20-*-*-*-*-*-fontset-hirakaku20" nil t)
      (mapc
       #'(lambda (fontset)
           (set-fontset-font fontset 'japanese-jisx0208
                             '("Hiragino Kaku Gothic Pro" . "iso10646-1"))
           (set-fontset-font fontset 'katakana-jisx0201
                             '("Hiragino Kaku Gothic Pro" . "iso10646-1"))
           (set-fontset-font fontset 'japanese-jisx0212
                             '("Hiragino Kaku Gothic Pro" . "iso10646-1"))
           ) (list "fontset-hirakaku20" "fontset-hirakaku16" "fontset-hirakaku14"))

      (setq face-font-rescale-alist
            '(("^-apple-hiragino.*" . 1.2)
              (".*osaka-bold.*" . 1.2)
              (".*osaka-medium.*" . 1.2)
              (".*courier-bold-.*-mac-roman" . 1.0)
              (".*monaco cy-bold-.*-mac-cyrillic" . 0.9)
              (".*monaco-bold-.*-mac-roman" . 0.9)
              ("-cdac$" . 1.3)))

      (let ((my-fontset "fontset-hirakaku20")
            ;; (my-fontset "fontset-hiramaru20")
            )
        (set-default-font my-fontset)
        (add-to-list 'default-frame-alist `(font . ,my-fontset)))
      )
  
  ;; carbon emacs (emacs 22) の設定
  (progn
    (require 'carbon-font)
    ;; ...................................................
    ;; ...................................................
    ))