[LSP42] Eiffel
(この記事はLISP Implementation Advent Calendar 18日目のためのエントリです。)
EiffelでLISPを作りました。
https://github.com/zick/EiffeLisp

動機
今年の春、訳あって42個のプログラミング言語でLISP処理系を実装することになりました。これはその31個目です。
Eiffelは名前を知っているので選んだという感じです。Eiffelのことはほとんど知りませんでしたが、スクリプト言語のように気軽に書けるものではないと考えていたのでずっと後回しにしてきました。しかし、実装に使った言語がいよいよ30を突破して、言語を選ぶのが非常に難しくなってきたので名前を知ってる言語はなんでも使ってしまおうと思いました。
外観
まずはこのプログラムをご覧ください。
class CONS
inherit
LOBJ
create
make_cons
feature
car: LOBJ assign set_car
cdr: LOBJ assign set_cdr
set_car(x: LOBJ)
do
car := x
end
set_cdr(x: LOBJ)
do
cdr := x
end
make_cons(a: LOBJ; d: LOBJ)
do
car := a
cdr := d
end
end
「あ、この文法、教科書で見たやつだ!」と私は思いました。なんというか、ドキュメントをそのままプログラムにしたような感じといいますか、簡潔に言うと「古臭い」です。クラス名がすべて大文字なのも古臭くていい感じです。
オブジェクト指向
Eiffelはずばり、
「俺はオブジェクト指向言語だ! プログラムを書きたかったらまずクラスを作れ! クラスを作るたびにファイルを作れ! ヒャッハー!!!」
が完全に当てはまってしまいました。上記 CONS クラスのために新たなファイルを作る必要がありますし、 CONS のベースクラスである LOBJ クラスのためにも新たなファイルを作る必要があります。
class LOBJ end
この2行だけで1つのファイルです。正直どうかと思います。でも、この文法とは妙にマッチしているような気もしました。
Void-safety
まずは次のプログラムをごらんください。
nreverse(l: LOBJ): LOBJ
local
lst: LOBJ
tmp: LOBJ
do
Result := kNil
from
lst := l
until
lst = kNil
loop
if attached {CONS} lst as c then
tmp := c.cdr
c.cdr := Result
Result := lst
lst := tmp
else
lst := kNil -- break
end
end
end
「うわっ、この文法、パパの枕の臭いがする!」という話ではなく、見るべくは if attached {CONS} lst as c then のところです。型がCONSか確認しているのですが、同時に Void (他の言語で言うNULL) でないことも確認しています。Eiffelは値がVoidになり得るかどうかをコンパイル時に確認しており、Voidの可能性がある場合にチェックなしで中身にアクセスしようとするとコンパイルエラーになります。見た目はともかくよく出来てます。
ちなみに、この attached は昔のEiffelには無かったようで、古い資料には載っていません。具体的には日本語版Wikipediaとそのリンク先。資料のとおりに書いたのにコンパイルエラーになったときは非常に悲しかったです。
コンパイル時の型チェック
上のVoid-safetyの話からも分かる通り、Eiffelはコンパイル時に色々なチェックをします。未初期化の変数の使用もちゃんと関数を超えてチェックします。ただ、配列の境界などはチェックしてくれません。「そのためには依存型を云々……」などという難しい話ではなく、配列の index が1-originなのにリテラルの 0 を書いてもコンパイルエラーにならないのはちょっと。
TUPLE
Eiffelにはタプルがあって、["ABC", 123] と書くと TUPLE [STRING, INTEGER] という型になるようです。しかし、タプルから要素を取り出す item という関数の型は問答無用で ANY (すべてのベースとなる型) になります。型はどうした。マニュアルでTUPLEを調べてみると integer_item とか boolean_item という関数があり、それぞれ INTEGER, BOOLEAN を返すようです。正直意味が分かりません。なんなんだ、これ。
契約
Eiffelといえば契約による設計ですね。(私を含め)Eiffelがどんな言語なのかほとんど知らない人も「Eiffelといえば契約による設計」と呪文のように覚えている人は多いのではないでしょうか。しかし、私にとっての目的は「なるべく早く42個の言語でLISPを実装する」ことだけなので、LISPを作るのに不要な機能は積極的にスルーしたので、契約による設計を意識することは一切ありませんでした。マニュアルを読んでる時に invariant とか ensure とか書いてるあたりがきっとそうなんだろうなーと思ったり、それっぽいコンパイルエラーをみって、きっとこれがそうなんだろうと思いつつ全てスルーしました。
小学生並みの感想
もっと勉強しないといけないと思いました。