Hatena::ブログ(Diary)

あどけない話

2008-02-08

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)する必要があります。

参考:

くろいしくろいし 2008/02/08 13:06 数値が24ビットまでで嫌って誰か言ってた記憶があります。

kazu-yamamotokazu-yamamoto 2008/02/08 13:12 そうそう。32ビットを仮定している MD5 なんかを Elisp で書くと、泣きそうになります。今は、関数 md5 が提供されているからいいんですけど。

みずしまみずしま 2008/02/09 13:45 > 型変数があれば、複数の型を扱う関数に対し、型ごとに動作を登録していけます。

型変数(type variable)という用語をこのような意味で
使うのはやめておいた方が無難だと思います。というのは、
型変数というのは、一般的には静的型付けの言語に
おいて、その内容として型を当てはめることができる
ような変数のことを指す用語だからです。

kazu-yamamotokazu-yamamoto 2008/02/09 21:44 みずしまさん、
コメントありがとうございます。
この場合、どういう用語を使うとよいでしょうか?

みずしまみずしま 2008/02/09 22:37 確認なんですが、ここで指しているのはいわゆる
オブジェクト指向言語におけるポリモーフィズム
(実行時の型情報などに基づいて処理を行うメソッドを
選択する)に相当する機能ですよね?だとすると、
総称関数(ジェネリックファンクション)などと呼ぶのが
(特にLisp界隈では)一般的なようです。

kazu-yamamotokazu-yamamoto 2008/02/12 12:30 みずしまさんの意見に納得できたので、型変数を総称関数に変えておきました。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証