Quantcast

[ruby-dev:38392] Enumerable#gather_each

classic Classic list List threaded Threaded
47 messages Options
123
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

[ruby-dev:38392] Enumerable#gather_each

Tanaka Akira-3
ときに、複数行をまとめて扱いたいことがあります。

たとえば、RD でインデントされた部分を取り出すとか、IO のパラ
グラフモードっぽいこととか、個人的には最近 chkbuild でログの
一部をソートするという必要がありました。

しかし、Enumerable でそういうまとまりを扱うメソッドは
each_slice しかなくて、柔軟なことは出来ません。

ファイルを全部読み込んで正規表現でやるというのはひとつの案で
すが、ファイルがとても大きいかもしれないとなるとやりたくない
ことがあります。
(実際 chkbuild のログはとても大きくなることがあります)

では自分で書くか、というと、これがなかなかきれいにかけません。
例えば、インデントされた部分を取り出すにはその直後のインデン
トされていない部分まで読まないとまとまりが判断できなくて、
IO#each_line で書こうとするとけっこう面倒です。ひとつの行が
インデントされているかどうかを調べるのは行頭が空白かどうかを
調べるだけで簡単にできるのですが、その結果に従って連続した行
をまとめるのが厄介です。

ここで、インデントされているかどうかとか、パラグラフだったら
空行かどうかとか、個々の要素を分類するところは問題によって異
なるのですが、その後の分類結果にしたがって連続した要素をまとめ
るのは共通しているので、Enumerable にメソッドとしてあっても
いいんじゃないかと思います。

というわけで、Enumerable#gather_each(arg) {|ary| ... } の提
案です。引数には Proc を与えて、これは各要素について呼ばれ、
結果として分類結果を返します。gather_each はその分類結果が等
しい連続した要素を配列としてまとめて yield します。

なお、分類結果が nil というのは特別扱いで、その要素は単独で
まとまりとなることを示します。

Ruby での実装を以下に示します。

module Enumerable
  def gather_each(arg)
    prev_value = prev_elts = nil
    self.each {|e|
      v = arg.call(e)
      if prev_value == nil
        if v == nil
          yield [e]
        else
          prev_value = v
          prev_elts = [e]
        end
      else
        if v == nil
          yield prev_elts
          yield [e]
          prev_value = prev_elts = nil
        elsif prev_value == v
          prev_elts << e
        else
          yield prev_elts
          prev_value = v
          prev_elts = [e]
        end
      end
    }
    if prev_value != nil
      yield prev_elts
    end
  end
end

たとえば、lib/scanf.rb には RD なドキュメントが入っていて、
以下のようにするとインデントされたコード例の部分をひとつにま
とめることができます。

arg = lambda {|l| /\A\=~ l ? true : nil }
open("lib/scanf.rb") {|f|                                                        
  f.gather_each(arg) {|lines| pp lines }                  
}
=>
["# scanf for Ruby\n"]
["#\n"]
["# $Release Version: 1.1.2 $\n"]
...
["the return array (or yielded to the block, if a block was
given).\n"]
["\n", "\n"]
["==Basic usage\n"]
["\n",
 "   require 'scanf.rb'\n",
 "\n",
 "   # String#scanf and IO#scanf take a single argument (a
 format string)\n",
 "   array = aString.scanf(\"%d%s\")\n",
 "   array = anIO.scanf(\"%d%s\")\n",
 "\n",
 "   # Kernel#scanf reads from STDIN\n",
 "   array = scanf(\"%d%s\")\n",
 "\n"]
["==Block usage\n"]
["\n"]
...

パラグラフモードっぽく、空行で区切られた部分をまとめたいなら
以下のようにできます。

arg = lambda {|l| l == "\n" }      
open("lib/scanf.rb") {|f|
  f.gather_each(arg) {|lines| pp lines }
}
=>
["# scanf for Ruby\n",
 "#\n",
 "# $Release Version: 1.1.2 $\n",
 "# $Revision: 22784 $\n",
 "# $Id: scanf.rb 22784 2009-03-06 03:56:38Z nobu $\n",
 "# $Author: nobu $\n",
 "#\n",
 "# A product of the Austin Ruby Codefest (Austin, Texas,
 August 2002)\n"]
["\n"]
["=begin\n"]
["\n"]
["=scanf for Ruby\n"]
["\n"]
["==Description\n"]
["\n"]
["scanf for Ruby is an implementation of the C function
scanf(3),\n",
 "modified as necessary for Ruby compatibility.\n"]
["\n"]
...

どうでしょう?

なお、複数行をまとめる方法として使いそうなものは、まとめる先
頭要素を検出する方法を指定するとか、他にもいくつかあるように
思います。たとえば ChangeLog や mbox を扱うのには、先頭要素
を指定するのがいいでしょう。そういうものはまた別のメソッドと
して作るのがいいのではないかと思います。
--
[田中 哲][たなか あきら][Tanaka Akira]

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

[ruby-dev:38393] Re: Enumerable#gather_each

Tanaka Akira-3
書き忘れました。

In article <[hidden email]>,
  Tanaka Akira <[hidden email]> writes:

> Ruby での実装を以下に示します。

これは Ruby の実装をそのまま使うという意味ではなく、仕様につ
いて議論する道具です。

C での実装はまだですが、これからやる予定です。
--
[田中 哲][たなか あきら][Tanaka Akira]

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

[ruby-dev:38394] Re: Enumerable#gather_each

ujihisa
In reply to this post by Tanaka Akira-3
ujihisaと申します。

一度に二つの手続きを渡すのではなく、一つのブロックを受け取りEnumeratorを返すgatherと、従来のeachに分けた方が自然になるのではないでしょうか。

元々の例:
  f.gather_each(lambda {|l| l == "\n" }) {|lines| pp lines }
  f.gather_each(->(l){ l == "\n" }) {|lines| pp lines }

提案例:
  f.gather {|i| i == "\n" }.each {|lines| pp lines }

あるいはその否定形で
  f.gather {|i| i != "\n" }.each {|lines| pp lines }

私としては最後の例が一番自然に見えるのですが、どうなのでしょうか。

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

[ruby-dev:38395] Re: Enumerable#gather_each

Tanaka Akira-3
In article <[hidden email]>,
  ujihisa <[hidden email]> writes:

> 一度に二つの手続きを渡すのではなく、一つのブロックを受け取りEnumeratorを返すgatherと、従来のeachに分けた方が自然になるのではないでしょうか。
>
> 元々の例:
>   f.gather_each(lambda {|l| l == "\n" }) {|lines| pp lines }
>   f.gather_each(->(l){ l == "\n" }) {|lines| pp lines }
>
> 提案例:
>   f.gather {|i| i == "\n" }.each {|lines| pp lines }
>
> あるいはその否定形で
>   f.gather {|i| i != "\n" }.each {|lines| pp lines }

なるほど、それはあり得るかもしれません。

gather_each を使って gather を実装すると以下のようになるでしょうか。

module Enumerable
  def gather(&b)
    enum_for(:gather_each, b)
  end
end

gather_each なしで gather を実装できるかというとちょっとわか
りません。

> 私としては最後の例が一番自然に見えるのですが、どうなのでしょうか。

このケースでは否定してもしなくても結果は同じなので自然に感じ
るほうで書けばいいんじゃないでしょうか。
--
[田中 哲][たなか あきら][Tanaka Akira]

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

[ruby-dev:38399] Re: Enumerable#gather_each

Akinori MUSHA
In reply to this post by Tanaka Akira-3
At Sat, 9 May 2009 15:30:20 +0900,
Tanaka Akira wrote:
> というわけで、Enumerable#gather_each(arg) {|ary| ... } の提
> 案です。引数には Proc を与えて、これは各要素について呼ばれ、
> 結果として分類結果を返します。gather_each はその分類結果が等
> しい連続した要素を配列としてまとめて yield します。
>
> なお、分類結果が nil というのは特別扱いで、その要素は単独で
> まとまりとなることを示します。

 少し仕様がわかりにくいように思います。繰り返しというストリームの
バッファという発想から、次のようなインターフェースを考案しました。
どうでしょうか。

class Enumerator
  class Buffer
    # status: 自由なメモ用途に提供
    attr_accessor :status

    def initialize(yielder)
      @buffer = []
      @yielder = yielder
      @status = nil
    end

    # バッファに追加
    def push(item)
      @buffer.push(item)
    end
    alias << push

    # バッファの最後の要素を取り出す
    def pop
      @buffer.pop
    end

    # バッファをクリア
    def clear
      @buffer.clear
      self
    end

    # バッファが空かどうか
    def empty?
      @buffer.empty?
    end

    # バッファに溜まった要素数
    def size
      @buffer.size
    end

    # バッファの内容を yield してクリア
    def flush
      @yielder.yield(@buffer)
      @buffer = []
      nil
    end
  end
end

module Enumerable
  def buffer(&block)
    Enumerator.new { |yielder|
      buffer = Enumerator::Buffer.new(yielder)
      each { |*args|
        case args.size
        when 0
          block.call(buffer)
        when 1
          block.call(args.first, buffer)
        else
          block.call(args, buffer)
        end
      }
      buffer.flush unless buffer.empty?    # ※終了時に自動flush
    }
  end
end

# 使用例:
#   要素をバッファに溜めていき、 nil が現れたら
#   そこまでのまとまりを yield する

p [1,2,3,nil,4,5,nil,6].buffer {|e, b|
  if e.nil?
    b.flush
  else
    b << e
  end
}.to_a

# 表示結果: [[1, 2, 3], [4, 5], [6]]

> なお、複数行をまとめる方法として使いそうなものは、まとめる先
> 頭要素を検出する方法を指定するとか、他にもいくつかあるように
> 思います。たとえば ChangeLog や mbox を扱うのには、先頭要素
> を指定するのがいいでしょう。そういうものはまた別のメソッドと
> して作るのがいいのではないかと思います。

 上記のインターフェースだと使用例のように区切りを渡さないことも
できますし、 yield を追加の前にするか後にするかも自由です。また、
status/status= を提供することで状態遷移を扱うことも支援できます。

 あとは、終了時のハンドラを定義すると上記の※印の代わりに実行
するようにするといいかなと思っています。

--
Akinori MUSHA / http://akinori.org/

attachment0 (202 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

[ruby-dev:38400] Re: Enumerable#gather_each

Yukihiro Matsumoto
In reply to this post by ujihisa
まつもと ゆきひろです

In message "Re: [ruby-dev:38394] Re: Enumerable#gather_each"
    on Sat, 9 May 2009 15:53:22 +0900, ujihisa <[hidden email]> writes:

|一度に二つの手続きを渡すのではなく、一つのブロックを受け取りEnumeratorを返すgatherと、従来のeachに分けた方が自然になるのではないでしょうか。

私もそう思います。ただ、Enumerableから「条件を満たす限り連続
した要素をまとめる」ことをgatherと呼ぶことになんとなく満足で
きません。gatherという単語からは全体から集めるような印象を受
けませんか? 私だけの感覚でしょうか。

だからとって代わりの単語を思いつかないわけですが。

だけだと情報があまりにもないので手元のシソーラスでgatherを引
いたところ、

accumulate, aggregate, amass, assemble, associate, bunch up,
capture, choose, close with, cluster, collect, concentrate,
congregate, convence, converge, corral, crowd, cull, draw,
draw in, flock, forgather, gang up, garner, get together,
group, hang around, hang out, heap, herd, hoard, huddle, ake
the scene, marshal, mass, meet, muster, pick, pile up,
pluck, poke, pour in, punch, rally, reunite, round up, scare
up, scrape together, show up, stack up, stockpile, swarm,
throng, unite

とありました。明らかに違うだろうとか、別の意味で使ってるとか
いうものもありますが、参考までに。

とはいえ、[ruby-dev:38399]のbufferというのも、気持ちは分かる
んですが、ちょっと違うような気がします。柔軟性は高いんですが、
なんか内臓が見えてる気がして。

                                まつもと ゆきひろ /:|)

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

[ruby-dev:38401] Re: Enumerable#gather_each

Akinori MUSHA
At Sun, 10 May 2009 06:00:08 +0900,
matz wrote:
> In message "Re: [ruby-dev:38394] Re: Enumerable#gather_each"
>     on Sat, 9 May 2009 15:53:22 +0900, ujihisa <[hidden email]> writes:
>
> |一度に二つの手続きを渡すのではなく、一つのブロックを受け取りEnumeratorを返すgatherと、従来のeachに分けた方が自然になるのではないでしょうか。
>
> 私もそう思います。ただ、Enumerableから「条件を満たす限り連続
> した要素をまとめる」ことをgatherと呼ぶことになんとなく満足で
> きません。gatherという単語からは全体から集めるような印象を受
> けませんか? 私だけの感覚でしょうか。

 最初に提案を見て split_by, slice_at, group_until のような名前を
思い付きましたが、そうして名前を付けてみると、件の挙動にぴったり
来る名前がないというよりむしろ、それらの名前から想起される機能が
ある程度の幅を持つということが本質なのではないかと気づきます。

 すなわち、田中さんも触れた通り、ヘッダ行の場合はそこから始まり、
フッタ行の場合はそこで終わり、区切り行の場合は読み捨てる、という
ような複数の要求が想定でき、それら使い分けができる柔軟性が必要
なのではないかということです。

> とはいえ、[ruby-dev:38399]のbufferというのも、気持ちは分かる
> んですが、ちょっと違うような気がします。柔軟性は高いんですが、
> なんか内臓が見えてる気がして。

 内臓というのはどのあたりのことでしょうか。すっきりしていると
思うんですが。Ruby って大体の部分において内臓は見られますよね。

--
Akinori MUSHA / http://akinori.org/

attachment0 (202 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

[ruby-dev:38402] Re: Enumerable#gather_each

NARUSE, Yui-2
成瀬です。

Akinori MUSHA wrote:

> At Sun, 10 May 2009 06:00:08 +0900,
> matz wrote:
>> In message "Re: [ruby-dev:38394] Re: Enumerable#gather_each"
>>     on Sat, 9 May 2009 15:53:22 +0900, ujihisa <[hidden email]> writes:
>>
>> |一度に二つの手続きを渡すのではなく、
>> |一つのブロックを受け取りEnumeratorを返すgatherと、
>> |従来のeachに分けた方が自然になるのではないでしょうか。
>>
>> 私もそう思います。ただ、Enumerableから「条件を満たす限り連続
>> した要素をまとめる」ことをgatherと呼ぶことになんとなく満足で
>> きません。gatherという単語からは全体から集めるような印象を受
>> けませんか? 私だけの感覚でしょうか。
>
>  最初に提案を見て split_by, slice_at, group_until のような名前を
> 思い付きましたが、そうして名前を付けてみると、件の挙動にぴったり
> 来る名前がないというよりむしろ、それらの名前から想起される機能が
> ある程度の幅を持つということが本質なのではないかと気づきます。

gather_byとかsplit_before/afterとか色々悩んだんですが、
これの動きって、divide lines according to proc's return valueじゃないですかね。
つまり、divide_eachとか。
集めると言うよりも、分けるイメージ

>  すなわち、田中さんも触れた通り、ヘッダ行の場合はそこから始まり、
> フッタ行の場合はそこで終わり、区切り行の場合は読み捨てる、という
> ような複数の要求が想定でき、それら使い分けができる柔軟性が必要
> なのではないかということです。
>
>> とはいえ、[ruby-dev:38399]のbufferというのも、気持ちは分かる
>> んですが、ちょっと違うような気がします。柔軟性は高いんですが、
>> なんか内臓が見えてる気がして。
>
>  内臓というのはどのあたりのことでしょうか。すっきりしていると
> 思うんですが。Ruby って大体の部分において内臓は見られますよね。

わたしもバッファを用意してーというのは考えたのですが、
田中さんのgather_eachで可能なことが、
gather_eachより複雑になってしまったらダメなんじゃないですかね。

田中さんのgather_eachでできることは、これ以上概念を増やさず
ほぼ同等の記述量でこなせつつ、自由度もあげられないといけないのかなと。

RubyなんでBufferみたいな概念を導入しても意外とすっきりするんですが、
そこで甘えちゃいけないんじゃないかと思うのですよ。

# というか、これってバッファは別に提供して、
# Enumerable版injectでやるべきな気も


> なお、複数行をまとめる方法として使いそうなものは、まとめる先
> 頭要素を検出する方法を指定するとか、他にもいくつかあるように
> 思います。たとえば ChangeLog や mbox を扱うのには、先頭要素
> を指定するのがいいでしょう。そういうものはまた別のメソッドと
> して作るのがいいのではないかと思います。

で、考えたのですが、ChangeLogやmboxの場合に対応するには、
直前の要素の分類結果がわかればそれで足りるんじゃないですかね。
結局これらは、
* 特定の要素は自分が分類結果
* それ以外は直前の分類結果と同じ
というルールなので、
arg = lambda {|l, current| l =~ /^\S/ ? self : current }
みたいにすればよいかなぁと。

これならば田中さんのgather_eachで可能だったことは、
currentを読み捨てればいいだけなので全く同じコードで可能になります。

--
NARUSE, Yui  <[hidden email]>

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

[ruby-dev:38405] Re: Enumerable#gather_each

Tanaka Akira-3
In reply to this post by Akinori MUSHA
In article <[hidden email]>,
  "Akinori MUSHA" <[hidden email]> writes:

>  少し仕様がわかりにくいように思います。繰り返しというストリームの
> バッファという発想から、次のようなインターフェースを考案しました。

どのようにわかりにくかったでしょうか?

> どうでしょうか。

まず、gather_each に比べてコードが長くなってよろしくありません。

たとえば、最初に出したパラグラフの例は以下のように長くなってしまいます。

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 }
  }

gather_each:
  arg = lambda {|l| l == "\n" }
  open("lib/scanf.rb") {|f|
    f.gather_each(arg) {|lines| pp lines }
  }

また、gather_each は、ある要素に対する処理は、その要素とそれ
が属するまとまりのことだけを考えて書けばいいようにデザインし
てあるのですが、buffer では直前のまとまりを flush する必要が
あって、直前のまとまりについても考えなければいけません。
つまり、buffer のほうが考えることが多くなってよろしくありま
せん。

たとえば、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? にしないとい
けません。

こういう、直前がなんだったか、というのを考えなくていいという
のが gather_each の利点です。

あと、Buffer には status という機能がありますが、状態をどう
しても管理したいならレキシカルスコープの変数でいいんじゃない
でしょうか。たとえば、パラグラフの例は以下のように書いてもいい
わけです。

  open("lib/scanf.rb") {|f|
    status = nil
    f.buffer {|e, b|
      s = e == "\n"
      b.flush if status != nil && status != s
      status = s
      b << e
    }.each {|lines| pp lines }
  }

そして、gather_each でも同様に状態は扱えて、ChangeLog のエン
トリは以下のように処理できます。(gather を使ってみました)

  open("ChangeLog") {|f|
    i = 0
    f.gather {|l| i += 1 if /\A\S/ =~ l; i }.each {|lines| pp lines }
  }

なお、私としてはこれを勧めているわけではなくて、ユーザがそう
いう状態遷移を考えなくて済むというのが良いと思っています。
つまり、専用のメソッを作るほうが、状態を考えなくていいので良
いと思います。

>  上記のインターフェースだと使用例のように区切りを渡さないことも
> できますし、 yield を追加の前にするか後にするかも自由です。また、
> status/status= を提供することで状態遷移を扱うことも支援できます。

gather_each でも、要素を結果から除去する指定は出来てもいいか
な、という気はします。そのための値を分類結果に定義しておけば
可能で、nil をその意味にするか、あるいは :delete あたりにす
るか、なにがいいかな。

buffer はいろんなことができるようにするという意図が感じられ
ますが、現実的な用途の想定として、どんなものが考えられますか?

私は、gather_each とあともうひとつ ChangeLog みたいなものを
処理するものがあると、かなりの範囲の用途を扱えるのではないか、
と考えています。その推測が正しければ専用のメソッドのほうが便
利でしょう。
--
[田中 哲][たなか あきら][Tanaka Akira]

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

[ruby-dev:38406] Re: Enumerable#gather_each

Tanaka Akira-3
In reply to this post by Yukihiro Matsumoto
In article <[hidden email]>,
  Yukihiro Matsumoto <[hidden email]> writes:

> 私もそう思います。ただ、Enumerableから「条件を満たす限り連続
> した要素をまとめる」ことをgatherと呼ぶことになんとなく満足で
> きません。gatherという単語からは全体から集めるような印象を受
> けませんか? 私だけの感覚でしょうか。

うぅむ。そんなに悪くはないと思っていたのですが、良い名前があ
れば積極的に受け入れたいと思っています。
(あと、ChangeLog の類のためのメソッド名も)

> だからとって代わりの単語を思いつかないわけですが。

Unix だと uniq なんですが ruby だと uniq も全体だしなぁ。

> だけだと情報があまりにもないので手元のシソーラスでgatherを引
> いたところ、
>
> accumulate, aggregate, amass, assemble, associate, bunch up,
> capture, choose, close with, cluster, collect, concentrate,
> congregate, convence, converge, corral, crowd, cull, draw,
> draw in, flock, forgather, gang up, garner, get together,
> group, hang around, hang out, heap, herd, hoard, huddle, ake
> the scene, marshal, mass, meet, muster, pick, pile up,
> pluck, poke, pour in, punch, rally, reunite, round up, scare
> up, scrape together, show up, stack up, stockpile, swarm,
> throng, unite
>
> とありました。明らかに違うだろうとか、別の意味で使ってるとか
> いうものもありますが、参考までに。

この中だと、accumulate, aggregate, pour, unite あたりはどう
ですかね。

他には each_slice から語を借用して slice もしくは slice_by
はどうでしょうか?

あとは categorize というのも思いつきましたがこれは全体っぽい
なぁ。
--
[田中 哲][たなか あきら][Tanaka Akira]

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

[ruby-dev:38407] Re: Enumerable#gather_each

Tanaka Akira-3
In reply to this post by NARUSE, Yui-2
In article <[hidden email]>,
  "NARUSE, Yui" <[hidden email]> writes:

> で、考えたのですが、ChangeLogやmboxの場合に対応するには、
> 直前の要素の分類結果がわかればそれで足りるんじゃないですかね。

直前のはいらないんじゃないでしょうか。

ある並びがあって、そのなかでのまとまりの先頭要素が判別できれ
ば、そこで区切ればいいわけです。仮に slice_by とすると以下の
ような感じになります。

module Enumerable
  def slice_by
    i = 0
    self.gather {|e| i += 1 if yield e; i }
  end
end

open("mbox") {|f|
  f.slice_by {|l| l.start_with? "From " }.each {|lines| pp lines }
}
--
[田中 哲][たなか あきら][Tanaka Akira]

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

[ruby-dev:38411] Re: Enumerable#gather_each

NARUSE, Yui-2
In reply to this post by Tanaka Akira-3
成瀬です。

Tanaka Akira wrote:
> なお、私としてはこれを勧めているわけではなくて、ユーザがそう
> いう状態遷移を考えなくて済むというのが良いと思っています。
> つまり、専用のメソッを作るほうが、状態を考えなくていいので良
> いと思います。

なるほど、ユースケースごとに専用メソッドを用意する方針ですか。
そうすると確かに問題はどのようなユースケースについて、
どんな名前のメソッドを用意するかだけなわけですね。

>>  上記のインターフェースだと使用例のように区切りを渡さないことも
>> できますし、 yield を追加の前にするか後にするかも自由です。また、
>> status/status= を提供することで状態遷移を扱うことも支援できます。
>
> gather_each でも、要素を結果から除去する指定は出来てもいいか
> な、という気はします。そのための値を分類結果に定義しておけば
> 可能で、nil をその意味にするか、あるいは :delete あたりにす
> るか、なにがいいかな。

Array#compactとか、昔のArray#[]=nilとかを考えるとnilが自然な気もしますが、
それこそ両方用意した方がいいんじゃないですかね、特定のを除外するのとしないのを。

> 私は、gather_each とあともうひとつ ChangeLog みたいなものを
> 処理するものがあると、かなりの範囲の用途を扱えるのではないか、
> と考えています。その推測が正しければ専用のメソッドのほうが便
> 利でしょう。

まとめると、必要なのは
* 引数であるprocの戻り値ごとにまとめる基本メソッド
* 一部を捨てるメソッド
* 特定の要素の直前で分けるメソッド
* 特定の要素の直後で分けるメソッド
あたりなのでしょうか

--
NARUSE, Yui  <[hidden email]>

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

[ruby-dev:38412] Re: Enumerable#gather_each

Tanaka Akira-3
In article <[hidden email]>,
  "NARUSE, Yui" <[hidden email]> writes:

> なるほど、ユースケースごとに専用メソッドを用意する方針ですか。

Enumerable の場合、あるメソッドでできないことがあっても他で
実現できないことはないので、なんでもできなければならないとい
う要求は小さいですね。

> そうすると確かに問題はどのようなユースケースについて、
> どんな名前のメソッドを用意するかだけなわけですね。

もちろん、少ない知識と記述でたくさんの用途を満たせるに越した
ことはないので、どのあたりを狙うかが問題になります。

> Array#compactとか、昔のArray#[]=nilとかを考えるとnilが自然な気もしますが、
> それこそ両方用意した方がいいんじゃないですかね、特定のを除外するのとしないのを。

:delete と :singleton とかかなぁ

svn log とかを処理すると区切りは消したくなるので、

% svn log enum.c|./ruby -rpp -e '
sep = "------------------------------------------------------------------------\n"
ARGF.gather {|l| l == sep }.each {|lines| pp lines }
'
["------------------------------------------------------------------------\n"]
["r22552 | nobu | 2009-02-22 23:23:33 +0900 (Sun, 22 Feb 2009) | 1 line\n",
 "\n",
 "stripped trailing spaces.\n"]
["------------------------------------------------------------------------\n"]
["r21678 | nobu | 2009-01-20 06:47:48 +0900 (Tue, 20 Jan 2009) | 3 lines\n",
 "\n",
 "* array.c (take_items), enum.c (enum_zip): tries to
 convert to\n",
 "  array first.  [ruby-core:21442]\n",
 "\n"]
...

ここで
ARGF.gather {|l| l == sep ? : :delete : true }
にするとか。

> まとめると、必要なのは
> * 引数であるprocの戻り値ごとにまとめる基本メソッド
> * 一部を捨てるメソッド
> * 特定の要素の直前で分けるメソッド
> * 特定の要素の直後で分けるメソッド
> あたりなのでしょうか

どれが基本かどうかというのはとくに意識しなくていいんじゃない
ですかね。たぶん slice_by を使って gather を実現するのはでき
る気がします。

直後で分ける用途は充分にたくさんあるんですかねぇ。それは
よくわかりません。作ることになったときに名前に悩まないように
しておくのはいいかもしれませんが。直前を slice_before にして
おいて、必要になったときには slice_after を作るとか。

あと、専用メソッドを増やせば増やすほどいいというわけでもあり
ません。メソッドが増えるとどれが適切かユーザが選ばないといけ
ませんから。記述が繁雑にならないなら、ひとつのメソッドの機能
を増やすというのも選択肢になります。そうすれば、うまく動かな
かったときにメソッドを選ぶところからやり直すのでなく、ブロッ
クの中をいじるだけで修正していける可能性が増します。
--
[田中 哲][たなか あきら][Tanaka Akira]

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

[ruby-dev:38413] Re: Enumerable#gather_each

ujihisa
ujihisaといいます。

> > それこそ両方用意した方がいいんじゃないですかね、特定のを除外するのとしないのを。
> ARGF.gather {|l| l == sep ? : :delete : true }

ARGF.gather(false) {|l| l == sep }
というのはどうでしょう。

module Enumerable
  def gather(included = true)
    ...

無指定またはtrueならば「境界」も含み、falseならばその部分を除外する、と。

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

[ruby-dev:38414] Re: Enumerable#gather_each

NARUSE, Yui-2
In reply to this post by Tanaka Akira-3
成瀬です。

Tanaka Akira wrote:

> In article <[hidden email]>,
>   Yukihiro Matsumoto <[hidden email]> writes:
>
>> だけだと情報があまりにもないので手元のシソーラスでgatherを引
>> いたところ、
>>
>> accumulate, aggregate, amass, assemble, associate, bunch up,
>> capture, choose, close with, cluster, collect, concentrate,
>> congregate, convence, converge, corral, crowd, cull, draw,
>> draw in, flock, forgather, gang up, garner, get together,
>> group, hang around, hang out, heap, herd, hoard, huddle, ake
>> the scene, marshal, mass, meet, muster, pick, pile up,
>> pluck, poke, pour in, punch, rally, reunite, round up, scare
>> up, scrape together, show up, stack up, stockpile, swarm,
>> throng, unite
>>
>> とありました。明らかに違うだろうとか、別の意味で使ってるとか
>> いうものもありますが、参考までに。
>
> この中だと、accumulate, aggregate, pour, unite あたりはどう
> ですかね。

その4つだとaggregateが好みかなぁ。
まぁ、gatherの用法を見ていると大雑把な語のようにも見えるので、
わたしはgatherでもいいかなぁと思い始めてもいます。
http://dic.yahoo.co.jp/dsearch?enc=UTF-8&p=gather&dtype=1

辞書を操ると、「同種の物をまとめる」風の意味のbunchとsheafとかありますね。
http://dic.yahoo.co.jp/dsearch?enc=UTF-8&p=bunch&dtype=1
http://dic.yahoo.co.jp/dsearch?enc=UTF-8&p=sheaf&dtype=1
全体でなく適当に束にする感じ

> 他には each_slice から語を借用して slice もしくは slice_by
> はどうでしょうか?

直前で切るーだとslice_beforeとかの方がしっくり来るんですが、
田中さんのgatherをsliceなんちゃらにしてしまうと違う感じですね。

--
NARUSE, Yui  <[hidden email]>

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

[ruby-dev:38415] Re: Enumerable#gather_each

NARUSE, Yui-2
In reply to this post by ujihisa
成瀬です。

ujihisa wrote:

> ujihisaといいます。
>
>>> それこそ両方用意した方がいいんじゃないですかね、特定のを除外するのとしないのを。
>> ARGF.gather {|l| l == sep ? : :delete : true }
>
> ARGF.gather(false) {|l| l == sep }
> というのはどうでしょう。
>
> module Enumerable
>   def gather(included = true)
>     ...
>
> 無指定またはtrueならば「境界」も含み、falseならばその部分を除外する、と。

「無指定またはtrueならば」だと戻り値がtrueの時に除外したい場合に困ります。
まぁ、*argで拾ってやれば無指定の場合をきちんと拾えるので、
除外する結果を引数で与えるというのはありでしょうね。

--
NARUSE, Yui  <[hidden email]>

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

[ruby-dev:38416] Re: Enumerable#gather_each

Tanaka Akira-3
In reply to this post by ujihisa
In article <[hidden email]>,
  ujihisa <[hidden email]> writes:

> 無指定またはtrueならば「境界」も含み、falseならばその部分を除外する、と。

gather の仕様には境界という概念はないので、それはうまくない
かなぁ。

gather は単にブロックの値が同じで隣接しているものをまとめる
だけなので、どれが境界でどれがそうでないかとかという区別はし
ていないんです。

もちろん、特定のブロックの値に「境界」という意味をつけて、そ
れを取り除くかどうかを引数で指定するという仕様を考えることも
出来ますが、それだったら「境界」じゃなくて直接「取り除く」と
いう意味をつけたほうが簡単でしょう。

false に「取り除く」という意味をつけるというのも考えられなく
はありませんが。
--
[田中 哲][たなか あきら][Tanaka Akira]

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

[ruby-dev:38417] Re: Enumerable#gather_each

Akinori MUSHA
In reply to this post by Tanaka Akira-3
At Sun, 10 May 2009 10:08:47 +0900,
Tanaka Akira wrote:
> In article <[hidden email]>,
>   "Akinori MUSHA" <[hidden email]> 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/

attachment0 (202 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

[ruby-dev:38418] Re: Enumerable#gather_each

Tanaka Akira-3
In reply to this post by Tanaka Akira-3
In article <[hidden email]>,
  Tanaka Akira <[hidden email]> writes:

> C での実装はまだですが、これからやる予定です。

とりあえず C で実装してみたのでつけておきます。

以下のメソッドを実装してあります。

* Enumerable#gather_each
* Enumerable#gather
* Enumerable#slice_before

slice_before は Yielder を使ってあります。

gather も Yielder を使う形にすれば、gather_each をなくすこと
は可能だと思います。

% svn diff --diff-cmd diff -x '-u -p'
Index: enumerator.c
===================================================================
--- enumerator.c (revision 23381)
+++ enumerator.c (working copy)
@@ -14,6 +14,18 @@
 
 #include "ruby/ruby.h"
 
+static VALUE
+enum_values_pack(int argc, VALUE *argv)
+{
+    if (argc == 0) return Qnil;
+    if (argc == 1) return argv[0];
+    return rb_ary_new4(argc, argv);
+}
+
+#define ENUM_WANT_SVALUE() do { \
+    i = enum_values_pack(argc, argv); \
+} while (0)
+
 /*
  * Document-class: Enumerator
  *
@@ -45,6 +57,7 @@ struct yielder {
     VALUE proc;
 };
 
+static VALUE enumerator_allocate(VALUE klass);
 static VALUE generator_allocate(VALUE klass);
 static VALUE generator_init(VALUE obj, VALUE proc);
 
@@ -247,6 +260,61 @@ enum_each_with_object(VALUE obj, VALUE m
     return memo;
 }
 
+struct slice_before_arg {
+    VALUE separator_p;
+    VALUE prev_elts;
+    VALUE yielder;
+};
+
+static VALUE
+slice_before_ii(VALUE i, VALUE _argp, int argc, VALUE *argv)
+{
+    struct slice_before_arg *argp = (struct slice_before_arg *)_argp;
+
+    ENUM_WANT_SVALUE();
+
+    if (RTEST(rb_funcall(argp->separator_p, rb_intern("call"), 1, i))) {
+        if (!NIL_P(argp->prev_elts))
+            rb_funcall(argp->yielder, rb_intern("<<"), 1, argp->prev_elts);
+        argp->prev_elts = rb_ary_new3(1, i);
+    }
+    else {
+        if (NIL_P(argp->prev_elts))
+            argp->prev_elts = rb_ary_new3(1, i);
+        else
+            rb_ary_push(argp->prev_elts, i);
+    }
+
+    return Qnil;
+}
+
+static VALUE
+slice_before_i(VALUE yielder, VALUE enumerator, int argc, VALUE *argv)
+{
+    VALUE enumerable;
+    struct slice_before_arg arg;
+
+    enumerable = rb_ivar_get(enumerator, rb_intern("slice_before_enumerable"));
+    arg.separator_p = rb_ivar_get(enumerator, rb_intern("slice_before_separator_p"));
+    arg.prev_elts = Qnil;
+    arg.yielder = yielder;
+
+    rb_block_call(enumerable, id_each, 0, 0, slice_before_ii, (VALUE)&arg);
+    if (!NIL_P(arg.prev_elts))
+        rb_funcall(arg.yielder, rb_intern("<<"), 1, arg.prev_elts);
+    return Qnil;
+}
+
+static VALUE
+enum_slice_before(VALUE enumerable)
+{
+    VALUE enumerator = enumerator_allocate(rb_cEnumerator);
+    rb_ivar_set(enumerator, rb_intern("slice_before_enumerable"), enumerable);
+    rb_ivar_set(enumerator, rb_intern("slice_before_separator_p"), rb_block_proc());
+    rb_block_call(enumerator, rb_intern("initialize"), 0, 0, slice_before_i, enumerator);
+    return enumerator;
+}
+
 static VALUE
 enumerator_allocate(VALUE klass)
 {
@@ -862,6 +930,8 @@ Init_Enumerator(void)
     rb_define_method(rb_mEnumerable, "each_cons", enum_each_cons, 1);
     rb_define_method(rb_mEnumerable, "each_with_object", enum_each_with_object, 1);
 
+    rb_define_method(rb_mEnumerable, "slice_before", enum_slice_before, 0);
+
     rb_cEnumerator = rb_define_class("Enumerator", rb_cObject);
     rb_include_module(rb_cEnumerator, rb_mEnumerable);
 
Index: enum.c
===================================================================
--- enum.c (revision 23381)
+++ enum.c (working copy)
@@ -1793,6 +1793,141 @@ enum_cycle(int argc, VALUE *argv, VALUE
     return Qnil; /* not reached */
 }
 
+struct gather_each_st {
+    VALUE arg;
+    VALUE prev_value;
+    VALUE prev_elts;
+};
+
+static VALUE
+gather_each_i(VALUE i, VALUE _state, int argc, VALUE *argv)
+{
+    struct gather_each_st *statep = (struct gather_each_st *)_state;
+    VALUE v;
+    VALUE singleton, reject;
+
+    ENUM_WANT_SVALUE();
+
+    v = rb_funcall(statep->arg, rb_intern("call"), 1, i);
+
+    singleton = ID2SYM(rb_intern("singleton"));
+    reject = ID2SYM(rb_intern("reject"));
+
+    if (v == singleton) {
+        if (!NIL_P(statep->prev_value)) {
+            rb_yield(statep->prev_elts);
+            statep->prev_value = statep->prev_elts = Qnil;
+        }
+        rb_yield(rb_ary_new3(1, i));
+    }
+    else if (!RTEST(v) || v == reject) {
+        if (!NIL_P(statep->prev_value)) {
+            rb_yield(statep->prev_elts);
+            statep->prev_value = statep->prev_elts = Qnil;
+        }
+    }
+    else {
+        if (NIL_P(statep->prev_value)) {
+            statep->prev_value = v;
+            statep->prev_elts = rb_ary_new3(1, i);
+        }
+        else {
+            if (rb_equal(statep->prev_value, v)) {
+                rb_ary_push(statep->prev_elts, i);
+            }
+            else {
+                rb_yield(statep->prev_elts);
+                statep->prev_value = v;
+                statep->prev_elts = rb_ary_new3(1, i);
+            }
+        }
+    }
+
+    return Qnil;
+}
+
+/*
+ *  call-seq:
+ *     enum.gather_each(arg) {|ary| ... } => nil
+ *     enum.gather_each(arg)              => enumerator
+ *
+ *  Iterates for each gathered elements of _enum_.
+ *
+ *  This method gathers consecutive elements which
+ *  _arg_.call(_element_) returns a same value.
+ *
+ *  The following values has special meaning:
+ *  - nil, false and :reject specifies that gathered elements is not yielded.
+ *  - :singleton specifies the element should be gathered only itself.
+ *
+ *  The gathered element is yielded as an array.
+ *
+ *  If the block is not given, an enumerator is returned.
+ *
+ *    (1..10).gather_each(lambda {|n| n & 2 }) {|a| p a }
+ *    #=> [1]           # 1 & 2 = 0
+ *    #   [2, 3]        # 2 & 2 = 3 & 2 = 1
+ *    #   [4, 5]        # 4 & 2 = 5 & 2 = 0
+ *    #   [6, 7]        # 6 & 2 = 7 & 2 = 1
+ *    #   [8, 9]        # 8 & 2 = 9 & 2 = 0
+ *    #   [10]          # 10 & 2 = 1
+ *
+ *    # gather indented blocks.
+ *    io.gather_each(lambda {|line| /\A\s/ =~ line }) {|lines| pp lines }
+ *
+ */
+static VALUE
+enum_gather_each(int argc, VALUE *argv, VALUE self)
+{
+    struct gather_each_st state;
+
+    rb_scan_args(argc, argv, "01", &state.arg);
+
+    RETURN_ENUMERATOR(self, argc, argv);
+
+    state.prev_value = Qnil;
+    state.prev_elts = Qnil;
+
+    rb_block_call(self, id_each, 0, 0, gather_each_i, (VALUE)&state);
+
+    if (state.prev_value != Qnil)
+        rb_yield(state.prev_elts);
+
+    return Qnil;
+}
+
+/*
+ *  call-seq:
+ *     enum.gather {|elt| ... } => enumerator
+ *
+ *  Creates an enumerator for iterating gathered elements of _enum_.
+ *
+ *  This method gathers consecutive elements which
+ *  the blocks returns a same value.
+ *
+ *  The following values has special meaning:
+ *  - nil, false and :reject specifies that gathered elements is not yielded.
+ *  - :singleton specifies the element should be gathered only itself.
+ *
+ *    (1..10).gather {|n| n & 2 }.each {|a| p a }
+ *    #=> [1]           # 1 & 2 = 0
+ *    #   [2, 3]        # 2 & 2 = 3 & 2 = 1
+ *    #   [4, 5]        # 4 & 2 = 5 & 2 = 0
+ *    #   [6, 7]        # 6 & 2 = 7 & 2 = 1
+ *    #   [8, 9]        # 8 & 2 = 9 & 2 = 0
+ *    #   [10]          # 10 & 2 = 1
+ *
+ *    # gather indented blocks.
+ *    io.gather {|line| /\A\s/ =~ line }.each {|lines| pp lines }
+ *
+ */
+static VALUE
+enum_gather(VALUE self)
+{
+    VALUE block = rb_block_proc();
+    return rb_enumeratorize(self, ID2SYM(rb_intern("gather_each")), 1, &block);
+}
+
 /*
  *  The <code>Enumerable</code> mixin provides collection classes with
  *  several traversal and searching methods, and with the ability to
@@ -1852,6 +1987,8 @@ Init_Enumerable(void)
     rb_define_method(rb_mEnumerable, "drop", enum_drop, 1);
     rb_define_method(rb_mEnumerable, "drop_while", enum_drop_while, 0);
     rb_define_method(rb_mEnumerable, "cycle", enum_cycle, -1);
+    rb_define_method(rb_mEnumerable, "gather_each", enum_gather_each, -1);
+    rb_define_method(rb_mEnumerable, "gather", enum_gather, 0);
 
     id_eqq  = rb_intern("===");
     id_each = rb_intern("each");
--
[田中 哲][たなか あきら][Tanaka Akira]

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

[ruby-dev:38419] Re: Enumerable#gather_each

Tanaka Akira-3
In reply to this post by Akinori MUSHA
In article <[hidden email]>,
  "Akinori MUSHA" <[hidden email]> writes:

> nil の扱いの特殊性(捨てるのでなくそれ単独で、という意味づけ)とか、

なるほど。これについてはいま議論がありますが、いくつかの値に
ついては特殊な意味があるということになれば、一般化されてそう
いうものだという気になるかもしれません。

> gather という名前のせいかも知れませんが同じ値が再出しても前とは
> 関係ないというあたりですかねえ。

これはまつもとさんの印象と同じですね。連続した値しかまとまら
ないという気分をかもしだす名前があったら提案していただけると
ありがたいです。

>  最初の例というのが
>> arg = lambda {|l| /\A\=~ l ? true : nil }
> で読めなかったのですが、 l == "\n" でしたか。

あぁ、すいません。そこは /\A\s/ =~ l です。

パラグラフの例は最初のメール [ruby-dev:38392] のもうちょっと
下に出てきます。

> これは
>
>         prev, s.status = s.status, (e == "\n")
>         b.flush if prev != b.status
>         b << e
>
> くらいで悪くはないと思います。b.status != nil のところは、上記の
> 「空なら flush しない」で手当てするとして。

それでも gather_each のほうがずっと短いですよね。

>  やりすぎかもしれませんが、 status/status= を提供するのなら、
> prev_status や status_changed? も用意するという手はあります。

私としては、状態変化をユーザが意識する必要はない用途は充分に
多いと考えています。

状態変化をそうやってサポートするのは、状態変化をユーザが意識
する必要がある用途には有用でしょう。しかし、まずそういう用途
が充分に多いのかという点について議論が必要ではないでしょうか。

>  区切るだけなら確かに1行で済みますが、実際にはサンプルコード辺か
> どうかを判定したり、前後の空行を除いたりと最終結果までの道のりは
> 長いので何とも言えません。要らない部分まで集めて(gather)いますが、
> 本当はもっと複雑な処理が必要なので buffer のようなものがあれば、
> 取捨や加工についても引き受けることができると思いました。

後でやればいいんじゃないでしょうか。gather はまとめるだけで
情報を捨てませんし。

>  上記の通り、実際に考えるべきことが後ろに残ると思うので、 gather
> 単体の提供する機能が中途半端に思えたのです。すなわち、インデント
> レベル等、分類の基準として計算した値(ブロックの評価値)を捨てて
> しまっていますが、この例でも、後段でまた必要になりそうですよね。

インデントの深さは、むしろ後から計算するほうがいいんじゃない
でしょうか。インデントされたブロックでは、複数の行のうち、もっ
とも浅いインデントが欲しそうですよね。

もし、どうしても分類の値が必要だということであれば、yield す
る配列にインスタンス変数としてつけておく (必要ならそれを参照
するアクセサも) のにはやぶさかではありません。

> ということであれば、 buffer を使って gather 等を実装するのは容易
> なので、複数のメソッドを用意するのなら、実装を共有するためにも
> buffer のような汎用のものを持つメリットがあるということになるの
> ではないでしょうか。

実装してみましたが、そういうものがなくてもそれほど面倒ではあ
りませんでした。

汎用のものを用意するのは、汎用のものが必要な例が出てきてから
でいいんじゃないでしょうか。

>  buffer は1回のイテレーションで複数の値を push したり複数回
> flush したり(あるいはしなかったり)でき、またイテレータ引数で
> なく任意の値を push できるので、 lexer などを実装できます。
>
>  というか、 scanf.rb をパースする例を見て、実際の延長上には
> lexer のようなものがあるんじゃないかと推測しました。

複雑なことができるからいろんな用途があるに違いない、という話
ではなく、具体的な例はありませんか?
--
[田中 哲][たなか あきら][Tanaka Akira]

123
Loading...