From: Akinori MUSHA Date: 2009-05-10T15:18:22+09:00 Subject: [ruby-dev:38417] Re: Enumerable#gather_each --pgp-sign-Multipart_Sun_May_10_15:18:45_2009-1 Content-Type: text/plain; charset=ISO-2022-JP At Sun, 10 May 2009 10:08:47 +0900, Tanaka Akira wrote: > In article <86r5yy2nrg.knu@iDaemons.org>, > "Akinori MUSHA" writes: > > >  少し仕様がわかりにくいように思います。繰り返しというストリームの > > バッファという発想から、次のようなインターフェースを考案しました。 > > どのようにわかりにくかったでしょうか? nil の扱いの特殊性(捨てるのでなくそれ単独で、という意味づけ)とか、 gather という名前のせいかも知れませんが同じ値が再出しても前とは 関係ないというあたりですかねえ。  引用を前後させてしまいますが、 > たとえば、ChangeLog をエントリ単位に処理するには、行頭が空白 > じゃなかったら flush すればいいというわけで以下のようにすれ > ばいいと考えるかもしれませんが、実はそれだけだと先頭に [] が > 表示されるというバグが発生してしまいます。 > > open("ChangeLog") {|f| > f.buffer {|e, b| > b.flush if /\A\S/ =~ e > b << e > }.each {|lines| pp lines } > } > > ちゃんとやるには条件を /\A\S/ =~ e && !b.empty? にしないとい > けません。 これは思いました。バッファが空(初期化状態)の場合は flush しても yield しないようにするべきですね。 > > どうでしょうか。 > > まず、gather_each に比べてコードが長くなってよろしくありません。 > > たとえば、最初に出したパラグラフの例は以下のように長くなってしまいます。  最初の例というのが > arg = lambda {|l| /\A\=~ l ? true : nil } で読めなかったのですが、 l == "\n" でしたか。 > buffer: > open("lib/scanf.rb") {|f| > f.buffer {|e, b| > s = e == "\n" > b.flush if b.status != nil && b.status != s > b.status = s > b << e > }.each {|lines| pp lines } > } これは prev, s.status = s.status, (e == "\n") b.flush if prev != b.status b << e くらいで悪くはないと思います。b.status != nil のところは、上記の 「空なら flush しない」で手当てするとして。  やりすぎかもしれませんが、 status/status= を提供するのなら、 prev_status や status_changed? も用意するという手はあります。 > gather_each: > arg = lambda {|l| l == "\n" } > open("lib/scanf.rb") {|f| > f.gather_each(arg) {|lines| pp lines } > }  区切るだけなら確かに1行で済みますが、実際にはサンプルコード辺か どうかを判定したり、前後の空行を除いたりと最終結果までの道のりは 長いので何とも言えません。要らない部分まで集めて(gather)いますが、 本当はもっと複雑な処理が必要なので buffer のようなものがあれば、 取捨や加工についても引き受けることができると思いました。 > また、gather_each は、ある要素に対する処理は、その要素とそれ > が属するまとまりのことだけを考えて書けばいいようにデザインし > てあるのですが、buffer では直前のまとまりを flush する必要が > あって、直前のまとまりについても考えなければいけません。 > つまり、buffer のほうが考えることが多くなってよろしくありま > せん。 > > こういう、直前がなんだったか、というのを考えなくていいという > のが gather_each の利点です。  上記の通り、実際に考えるべきことが後ろに残ると思うので、 gather 単体の提供する機能が中途半端に思えたのです。すなわち、インデント レベル等、分類の基準として計算した値(ブロックの評価値)を捨てて しまっていますが、この例でも、後段でまた必要になりそうですよね。 > そして、gather_each でも同様に状態は扱えて、ChangeLog のエン > トリは以下のように処理できます。(gather を使ってみました) > > open("ChangeLog") {|f| > i = 0 > f.gather {|l| i += 1 if /\A\S/ =~ l; i }.each {|lines| pp lines } > }  そこが妥協可能ならそれでもいいですね。buffer の引数で status の 初期値を与えるようなことも考えていましたが。 > なお、私としてはこれを勧めているわけではなくて、ユーザがそう > いう状態遷移を考えなくて済むというのが良いと思っています。 > つまり、専用のメソッを作るほうが、状態を考えなくていいので良 > いと思います。  なるほど。それと、再び引用が前後しますが、 > 私は、gather_each とあともうひとつ ChangeLog みたいなものを > 処理するものがあると、かなりの範囲の用途を扱えるのではないか、 > と考えています。その推測が正しければ専用のメソッドのほうが便 > 利でしょう。 ということであれば、 buffer を使って gather 等を実装するのは容易 なので、複数のメソッドを用意するのなら、実装を共有するためにも buffer のような汎用のものを持つメリットがあるということになるの ではないでしょうか。 > gather_each でも、要素を結果から除去する指定は出来てもいいか > な、という気はします。そのための値を分類結果に定義しておけば > 可能で、nil をその意味にするか、あるいは :delete あたりにす > るか、なにがいいかな。 > > buffer はいろんなことができるようにするという意図が感じられ > ますが、現実的な用途の想定として、どんなものが考えられますか?  buffer は1回のイテレーションで複数の値を push したり複数回 flush したり(あるいはしなかったり)でき、またイテレータ引数で なく任意の値を push できるので、 lexer などを実装できます。  というか、 scanf.rb をパースする例を見て、実際の延長上には lexer のようなものがあるんじゃないかと推測しました。 -- Akinori MUSHA / http://akinori.org/ --pgp-sign-Multipart_Sun_May_10_15:18:45_2009-1 Content-Type: application/pgp-signature Content-Transfer-Encoding: 7bit -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (FreeBSD) iEYEABECAAYFAkoGccUACgkQkgvvx5/Z4e4BiACfeqQ/00NePWcEYJvmzZAbobu+ xHUAn1Ad8PnX4PdbT0fZ+LgsOcvISsRN =Du7P -----END PGP SIGNATURE----- --pgp-sign-Multipart_Sun_May_10_15:18:45_2009-1--