Rubyとの連携を考える(3)
私がRubyで書いているLisp方言、 [Nendo]の開発状況続き。
Rubyの世界へのアクセス手段として引きつづき .(ドット)オペレータを実装してみた。
(ここに書いた仕様はおそらく後日変わると思うので注意)
実際にコードを書いてみると何か違う
(. 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メソッドのオプション) の方がいいかもしれない。