fletで関数を横取りする

この記事はEmacs Advent Calenderの4日目です。はじめてのadvent calenderで何を書けば良いか考えてたのですが、emacs(emacs lisp)ならでは*1機能を紹介することにします。

dynamic scope / lexical scope

最近使われる言語*2の多くがlexical scopeを採用しているのに対し、emacs-lispはdynamic scopeです。lexical-scopeは値の束縛が定義の際に決まるのに対し、dynamic-scopeは実行時に束縛値が定まります。

とりあえず、ここでlecial-scopeのschemeと比較してdynamic scopeとlexical scopeの違いの例を紹介します。

emacs-lisp(dynamic-scope)
(let ((x "in definition"))
  (defun f ()  
    (print x)))

(let ((x "in run part"))
  (f)) ;; => in run part
schem(lexcal-scope)
(define f
  (let ((x "in definition"))
    (lambda () (print x))))

(let ((x "in run part"))
  (f)) ;; => in definition

emacs-lisp(dynamic-scope)ではfを実行した際の束縛が、またscheme(lexical-scope)はfを定義した際の束縛がxの値になっています。

dynamic-scopeの利用法

dynamic-scopeだと何がうれしいのでしょうか?簡単な例を紹介します。

利用例1:設定をねじ曲げる

例えば、現在設定されている状態をねじ曲げることができます。buffer-read-onlyがtの時、通常bufferはread-onlyになり、バッファに文字がかけなくなります。当然insertも使えません。

(setq buffer-read-only t) ;; C-x C-eすると書けなくなります。

(let ((buffer-read-only nil))
  (insert "foo")) ;; 実行するとfooが挿入されます。

一方、ここで、letでbuffer-read-onlyをnilを束縛した環境ではinsertが使えます。しかし、以前として、このバッファはread-onlyのままです。

利用例2:定義された関数を握りつぶす

さらに、一時的にシンボルに束縛された関数の中身を横取りしてすげかえることもできます。fletを使うと便利です。以下のコードのtimerを有効にすると、1秒おきに、ミニバッファにメッセージが表示されます。そして、これは、M-xでコマンドを実行する時などミニバッファで文字を入力しているときにも続きます。

(setq timer 
      (run-with-timer 1 1 (lambda () (message "I'm working. (annoying message)")))) ;; 有効にするとミニバッファにメッセージを表示してきてうるさい

(require 'cl)

;;ミニバッファでの入力が上のtimerに邪魔される。
(read-string "read:")

(flet ((message () nil))
  ;;この中ではmessageの定義が置きかわる。
  (read-string "read:"))

このメッセージが正直に言ってウザいです。ミニバッファに入力してくれる時くらい静かにしてほしいですね。これをfletでmessageを一時的に何もしない関数に置き換えて上げることで、邪魔されることなくミニバッファで文字を入力することができるようになります。dynamic-scope++

使っているelispの嫌な点の変更などにfletを使ってみるのも良いかもしれません。

*1:本当にemacsならではかはぎもん

*2:あえて言語名はあげません