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秒内に 再度実行させても同じ位置で表示される。

0 件のコメント:

コメントを投稿