あどけない話

インターネットに関する技術的な話など

Emacs Lisp のダメなところ

Emacs Lisp をこよなく愛する僕の目から、Emacs Lisp がダメだと思うところをまとめておきます。

文化的な問題

Emacs Lisper の多くは、Lisp が好きで使っているのではなく、Emacs が好きだからしかたなく使っているのでしょう。本当は C で書きたいのに、無理して Lisp を利用している感じです。

そのため、Emacs に付いてくる Emacs Lisp のコードは、Lisp らしくないものがほとんどです。単に C での発想を Lisp で表現しています。

これらのコードは、読みこなせないぐらい関数が大きく、副作用のある部分とない部分が分離されていません。また高階関数を用いて、データ構造を走査するコードと実際に仕事をするコードを分離するという意識も低いようです。

GoogleMapReduceという論文のお陰で、Lisp写像関数(map)と畳込み関数(reduce)が、関数型言語圏外の人からも注目されるようになりました。Emacs Lisp には、mapcar はありますが、reduce が標準で提供されていないのは、あまりに残念です。('cl を require すると使えるようになります。)

多くの人が C っぽいコードを書くので、それを参考にする人も C っぽいコードを書くようになります。(僕もそうでした。)

参考:

末尾再帰の最適化がない

Emacs Lisp で、大きな繰り返しを再帰関数で実現するのは、現実的ではありません。すぐにスタックが溢れてしまいます。しょうがないので、while 文と代入を多用して繰り返しを実装します。

参考:

クロージャがない

AJAX によって再発見された JavaScript は、クロージャを再発見しました。クロージャがあれば、関数を生成する関数などが簡単に実現できます。以下は、Scheme で加算器を生成する関数の例です。

(define (make-accumulator increment)
  (lambda (n) (+ n increment)))
(define add2 (make-accumulator 2))
(add2 5) ;; => 7

RMSEmacs Lisp を考案したときには、すでに Scheme がありましたが、静的スコープは選ばすに、動的スコープを選びました。どうも、静的スコープは遅いと考えているようです。

Elisp の info 「11.9.1 Scope」より:

Emacs Lisp uses dynamic scoping because simple implementations of lexical scoping are slow.

また、バッファローカル変数などの問題もありますから、全面的に静的スコープを採用することは難しいでしょうね。

参考:

正規表現がダサい

Emacs Lisp正規表現は、バックスラッシュのお化けです。以下は、どういう意味だか理解できますか?

(looking-at "\\(\\*\\*\\|\\?\\?\\)")

こうなってしまうのは、2つの理由があります。

  • '(' などのよく使う機能もバックスラッシュを使って '\(' と書かなければならない
  • 正規表現リテラルがなく、文字列で表現するので、さらにバックスラッシュを加えて '\\(' を書く必要がある

参考:

モジュールで名前空間を分けることができない

Emacs Lisp ではファイル単位でモジュールを構成しますが、名前空間は全体でフラットです。ですから、必ず名前の先頭に、自分で慎重に選んだプレフィックスを付ける必要があります。コーディング中に長い名前を入力しなければならないのはストレスです。

スレッドがない

Emacs Lisp で非同期性を実現するには、TCP コネクションを開くか、外部コマンドを起動する必要があります。非同期に処理したい作業が、TCP を使うサーバ相手だけならいいのですが、ローカルの作業を Emacs Lisp だけで非同期にやりたいと思ってもできません。外部コマンドを C などで実装する必要があります。

参考:

仕様がよく変わる

Emacs Lisp の仕様はよく変わります。Emacs のある特定のバージョンだけを対象にコードを書くのは簡単ですが、複数のバージョンを対象にすると泣きそうになります。XEmacs も対象に含めると、事態はさらに悪化します。

この違いを吸収するライブラリが提供されており、それを require しているコードが実行されると、プレフィックスが付いていない関数が気付かない間に定義されます。すると、自前で差分を考慮するコードが動かなくなり、とっても悲しい思いをします。

Lint

Emacs Lisp のバイトコンパイラーは、未使用のローカル変数を検知してくれません。しょうがないので、この機能を持つ XEmacs でバイトコンパイルして検査します。

λ計算遊びがやりにくい

Emacs Lisp では、変数名と関数名の空間が分かれています。そのため、変数に関数を代入しておいて、それを単に名前で呼び出すことはできません。たとえば、以下のコードは動きません。

(let ((add2 (lambda (x) (+ x 2))))
  (add2 3))

呼び出すためには、funcall を使います。また、関数から返された関数を呼び出すのにも funcall を使わなければなりません。たとえば、以下のコードもエラーになります。

(((lambda () (lambda (x) (+ x 2)))) 3)

これらの制約が通常のプログラミングで問題になることは、ほとんどありません。しかし、λ計算をシミュレートさせようとすると、嫌になります。

Scheme では、どちらのコードも動きますから、λ計算をシミュレートさせるときは scheme を選ぶのが得策です。

参考:

リードマクロがない

リードマクロがないので、前置構文の制約から抜け出せません。たとえば、ハッシュをキーで検索するのを以下のように書ければ便利ですが、それは無理です。

hash=>key

参考:

構造体がない

構造体が標準で提供されていません。'cl を require すれば、Common Lisp の defstruct が使えるようになりますが、僕は setter がないこのマクロは好きになれません。

参考:

多相性がない

総称関数があれば、複数の型を扱う関数に対し、型ごとに動作を登録していけます。

しかし、Emacs Lisp には型変数が提供されていません。ですから、複数の型を扱う関数は、一カ所で定義して内部で型に応じてスイッチ(cond)する必要があります。

参考: