[Gauche-devel-jp] 辞書とイテレータ

Back to archive index

Shiro Kawai shiro****@lava*****
2003年 1月 29日 (水) 19:18:34 JST


Shiroです。
うーんやっぱり難しい問題なのかな。書いててなかなかうまくまとまり
ません。とりあえず冬樹さんのリストをとっかかりにつらつら書いてみます。

From: Kimura Fuyuki <fuyuk****@hadal*****>
Date: Thu, 23 Jan 2003 15:44:30 +0900

> 1. call-with-iterator に与えられた keys から重複を取り除くのはけっこう
>    めんどう or 重たい処理かもしれない。

> 1については、keys を delete-duplicates にかけてしまうのが(それで済むの
> なら)一番単純なやり方になると思います。検索しながら重複を排除するよう
> なうまいやり方があればいいのですが。

そうですね。小黒さんも言われるように、重複を除きたかったら
callerがdelete-duplicatesを呼ぶだけで良いので、call-with-iterator
は素直に渡されたkeyを順に処理するので良いかもしれません。
(それならkeysは可変長引数でなくてリスト渡しの方がいいかなあ)。

> 2. 空のディクショナリに insert! する素直な手段がないような。

insert! については、例えば文字セットを何かのオブジェクトにマップする
ディクショナリにおいて、「小文字に対応するエントリがあれば
大文字に対応するエントリも作製する」みたいな用途に使えるかと
思っていたのですが、よく考えたらイテレータのそもそもの目的は
「イテレート中はエントリへのポインタを持っているんだから
有効活用したい」ということだったわけで、insert!はあんまり
メリットがありませんね。つい、call-with-iteratorを
トランザクションモデルと重ねてしまったので iterateeの中で
insert!しなきゃ、と思ってしまったのですが、トランザクション処理
は分けた方が綺麗かな。それならinsert!は別に作れますし。

小黒さんのcall-with-builderを使う案も悪くはないんですが、
"builder" の名前からしても、オブジェクトでなくクラスを引数に
取ることからしても、 "builder" は何も無いところから何かを
作るという感じがしてしまいますね。call-with-updater!
もしくはcall-with-inserter!みたいなものを設けた方がいいかなあ。

まてよ、call-with-updaterとcall-with-iteratorがネストしたら
どうしよう。

> 3. case によるディスパッチはやっぱり格好悪い。

> 3はかなり一般的な問題だと思うので(gettext.scmでもやっている)、何か適当
> なユーティリティを提供したほうがいいのかもしれません。

そうっすねえ。このへんOO言語ならかえって悩まなくても
いいんでしょうが…
Gaucheのオブジェクトシステムだとこういうローカルな
メソッドバインディングは扱いにくいですね。

実行する側からだと、メッセージによってディスパッチする
手続きは効率もそんなに悪くないですし、素直だと思うんですよ。
問題がコードの字面だけなら、こんなマクロを使ってしまえば:

(define-syntax dispatching-lambda
  (syntax-rules ()
    ((_ method more ...)
     (dispatching-lambda-sub () () method more ...))))

(define-syntax dispatching-lambda-sub
  (syntax-rules (define)
    ((_ (symbol ...) (proc ...))
     (lambda (message . args)
       (case message
         ((symbol) (apply proc args)) ...
         (else (error "unknown message" message)))))
    ((_ (symbol ...) (procs ...) (define (method . args) . body) more ...)
     (dispatching-lambda-sub (symbol ... method)
                             (procs ... (lambda args . body))
                             more ...))
    ))

こんなふうに書けますね:

 (dispatching-lambda
   (define (get) ...)
   (define (next seed) ...)
   (define (update! val) ...)
   (define (delete!) ...)
   (define (insert! key val) ...))

こういうマクロをひとつ標準で持っておくと便利かもしれませんね。

> 4. SRFIとの名前の衝突はどうしたものか(remove!とかmap!とか)。

> 4は前から気になっていたのですが、無視していました(いい解決を思いつかな
> いから)。コレクションの fold みたいに再定義する手もありますが、ディク
> ショナリの場合は関数の意味が違っているのであまりやりたくありません
> (SRFIは結果を返すが、こっちのは副作用がすべて)。

dictionary-remove! もしくは dict-remove! ですかね。

引数でディスパッチされるのにdict-ってprefixをつけるのは
冗長であるとも言えるのですが、呼び出し形式やセマンティクスが
異なるメソッドは名前も分けておいた方が良いと思います。

このへんは、メソッドがクラスに従属する言語なら名前つけに
悩まなくて良さそうですが、実はうかつに異なるセマンティクスの
メソッドに同じ名前をつけると式  foo.remove! を見ただけでは
セマンティクスの判断が出来ないという落し穴があるのでどっこい
どっこいでしょう。

再定義は再定義で落し穴があってあまり使いたくないのです。
最初からfold等をgeneric functionにしておけばコレクションで
再定義しなくて済むのですが、メソッドディスパッチ無しの高速版が
欲しいという要求もありそうなので妥協しています。
メソッドキャッシュ等が実装されて、メソッドディスパッチの
ペナルティが手続き呼び出しに比べてそれほどでも無くなったら
foldやfor-each等の基本関数をgeneric functionにしてしまう
かもしれません。

> 5. 「現在の要素は削除されているか」みたいな状態を押さえておくのが面倒。

これはディクショナリの実装に依存してしまうので、call-with-iterator
の実装者側で面倒を見てもらうしかないですね。トランザクションモデルだと
「本当に削除」されるのはcall-with-iteratorを抜ける時になる可能性も
あるわけです。

> 6. remove! の pred などにはどんな引数を渡すべきか。
> 6は、a) (key . val) を渡すか、b) key val という二つの値を渡すかの選択
> です(コレクションとの連続性を気にするならa、そうでなければbか)。

コレクションで(cons key val)が渡されるのは他のコレクションオブジェクト
との一貫性だけの問題なので、常にkeyとvalがセットであるディクショナリ
ではわざわざconsる必要は無いでしょう。
性能も別引数の方が良いです。

ふと、複数のkeyでインデックスされるディクショナリ、というものも
頭をよぎったのですが… あまり複雑にしても仕方無いので置いときましょう。

まとまっていませんが、考えててもすぐには進まなさそうなので、
とりあえずここまでで。

--shiro






Gauche-devel-jp メーリングリストの案内
Back to archive index