kiyokaのブログアーカイブ

Archive of old blog posts

Rubyとの連携を考える(3)

私がRubyで書いているLisp方言、 [Nendo]の開発状況続き。 Rubyの世界へのアクセス手段として引きつづき .(ドット)オペレータを実装してみた。 img (ここに書いた仕様はおそらく後日変わると思うので注意)

実際にコードを書いてみると何か違う

(. a b) は Rubyのa.bという式に起きかえられるという単純なルールにしてみた。 以下は、実際の動作。

nendo> (macroexpand '(. Kernel open))
Kernel.open
nendo> (macroexpand '(. a size))
a.size
nendo> (macroexpand '(. (. a size) to_s))
a.size.to_s
nendo> (macroexpand '(. (. a size) to_s (. to_s to_i)))
a.size.to_s.to_s.to_i
nendo> (set! str "str")
"str"
nendo> (. str size)
3
nendo> str.size
3
nendo> (+ (. str size) 1)
4

何が問題か

問題は . (ドット)の第一引数にインスタンスが指定できないこと。

nendo> (define a "str")
"str"
nendo> a
"str"
nendo> (. a size)
3

ここまでは良い。が、 symbolまたは (. symbol symbol …) しか受けつけない様にしているので 例えば、次のようなコードは書けそうで書けない。

nendo> (. "str" size)
dot-operator requires symbol, but got str
	from ./init.nnd:292
	from ./init.nnd:267:in `initialize'
        .
        .
       ()
nendo> (. 3 to_s)
dot-operator requires symbol, but got 3
	from ./init.nnd:292
	from ./init.nnd:267:in `initialize'
        .
        .
       ()

Rubyを知っている人は、文字列などのリテラルを第一引数に指定したくなるだろうなあ。 制限がきつすぎるのかな。もうちょっと考えてみよう。

with-openを実装してみた

早速、 . (dot-operator)でRubyのライブラリを呼びだす例として、with-openを作ってみた。 使いかたは (with-open 手続き ファイル名) または、 (with-open 手続き ファイル名 openメソッドのオプション) と言う形式で記述する。 イメージはSchemeの with-input-from-file でいう with-系のユーティリティで、オープンしたRubyのFileオブジェクトを引数として手続きpredを呼びだす。 また、手続きpredの呼出後に、ファイルをクローズしてくれる。

(define (with-open pred . lst)
  (let1 len (length lst)
        (let1 f (cond
                 ((= 1 len)
                  (Kernel.open (car lst)))
                 ((< 1 len)
                  (Kernel.open (car lst) (cadr lst)))
                 (else
                  (error "with-open requires 1 or 2 arguments")))
              (let1 result (pred f)
                    (f.close)
                    result))))

実際に動かした。 t.txtにはこんなテキストデータが入っている場合、

bash-3.2$ cat t.txt
line1_A line1_B line1_C
line2_A line2_B line2_C
line3_A line3_B line3_C

このコードを実行すると

(with-open
 (lambda (x)
   (map
    (lambda (x)
      (x.chomp.split.to_list))
    (x.readlines.to_list)))
 "t.txt")

結果はこうなる。

(("line1_A" "line1_B" "line1_C") ("line2_A" "line2_B" "line2_C") ("line3_A" "line3_B" "line3_C"))

それにしても .to_list が毎回必要なのがカッコワルイか。 それと、引数の順番は (with-open ファイル名 手続き openメソッドのオプション) の方がいいかもしれない。