[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 とか書いてるあたりがきっとそうなんだろうなーと思ったり、それっぽいコンパイルエラーをみって、きっとこれがそうなんだろうと思いつつ全てスルーしました。

小学生並みの感想

もっと勉強しないといけないと思いました。

Leave a Reply