[LSP42] Smalltalk

(この記事はLISP Implementation Advent Calendar 19日目のためのエントリです。)

SmalltalkでLISPを作りました。
https://github.com/zick/SmalltalkLisp

動機

今年の春、訳あって42個のプログラミング言語でLISP処理系を実装することになりました。これはその32個目です。
Smalltalkといえば、開発環境と実行環境が共に同一のGUI環境という世界が特徴で、CUIのアプリケーションを作るのには不向きだと思っていたのですが、同僚から「GNU SmalltalkはCUIしかない」と聞いて、Smalltalkを使うことにしました(余談ですが、今のGNU SmalltalkはGUIの環境を備えています)。

OSX版の罠

GNU SmalltalkをOSXにインストールして、チュートリアルを読みながら簡単なプログラムをいくつかREPLから試し、「よしこれでLISPが書けるだろう」と思い、標準入力から一行取得するプログラムを書いたところでいきなりハマりました。 line := stdin nextLine. なんとこのプログラムを動かすとSIGABRTが飛んできます。意味が分かりません。試しにUbuntuにGNU Smalltalkをインストールして動かしてみるとちゃんと動きました。ちょっと酷い。

外観

Smalltalkといえば独特の文法でも有名ですね。

Util class >> nreverse: lst [
  | l tmp ret |
  l := lst.
  ret := kNil.
  [(l class) = Cons] whileTrue: [
    tmp := l cdr.
    l setCdr: ret.
    ret := l.
    l := tmp
  ].
  ^ret
]

Util class >> pairlis: lst1 with: lst2 [
  | l1 l2 ret |
  l1 := lst1.
  l2 := lst2.
  ret := kNil.
  [((l1 class) = Cons) & ((l2 class) = Cons)] whileTrue: [
    ret := Cons new: (Cons new: (l1 car) with: (l2 car)) with: ret.
    l1 := l1 cdr.
    l2 := l2 cdr
  ].
  ^self nreverse: ret
]

知識としては知っていたのですが、いざ書いてみるとなかなか混乱します。特にドットとコロンを書き忘れることが非常に多く、42個の言語の中でも私がコンパイルエラーを起こした回数No1の業績を残したような気もします。あと、上記pairlisの with のように引数の前に名前をつけるといいう文化も最後まで馴染みませんでした。SmalltalkやObjective-Cが好きな人は「これがいいんだ」と主張しますが、慣れていない私は何かと with とタイプする回数が増えるだけでした。

オブジェクト指向

さて、Smalltalkといえばオブジェクト指向です。しかし、これまで使った30以上の言語の中でオブジェクト指向っぽくLISPを作ったことがほとんどない私です。今回も「オブジェクト指向なんかに負けない!」と気合を入れてプログラムを書きました。

Object subclass: LObj [
  LObj class >> new: t [^super new]
]

LObj subclass: Nil [
  print [^'nil']
  eval: env [^self]
]

LObj subclass: Cons [
  | ar dr |
  Cons class >> new: a with: d [^super new setData: a with: d; yourself]
  setData: a with: d [ar := a. dr := d]
  setCar: a [ar := a]
  setCdr: d [dr := d]
  car [^ar]
  cdr [^dr]
  print [
    | obj ret first |
    obj := self.
    ret := ''.
    first := true.
    [(obj class) = Cons] whileTrue: [
      first = true ifTrue: [
        first := false
      ] ifFalse: [
        ret := ret, ' '
      ].
      ret := ret, ((obj car) print).
      obj := obj cdr
    ].
    (obj class) = Nil ifTrue: [
      ^'(', ret, ')'
    ] ifFalse: [
      ^'(', ret, ' . ', (obj print), ')'
    ]
  ]
  eval: env [^Util evalCompound: self env: env]
]

各クラスが print と eval というメソッドを持っています。オブジェクトにevalというメッセージと環境を渡すと値が得られるわけです。
書きやすいように作ろうと思ったら気が付いたらオブジェクト指向っぽくなってしまいました。なんか非常に悔しい。違う書き方をした方が速い気もしますが、面倒なのでそのままにしました。

終わらないごみ集め

Smalltalkのように柔軟な言語は引数の数が正しいかなどは、コンパイル時には分からず実行時にエラーになることがあります。私は柔軟さが好きなのでこれは仕方ないことだと思っているのですが、GNU Smalltalkは引数の数を間違えた時に、なぜかプログラムが停止せずにごみ集めが動き続けるという大惨事になることがあります。かなり酷い。

小学生並みの感想

まじめに勉強してないから書くことがない!

Leave a Reply