青木です。

  In mail "[ruby-dev:24020] Re: String#each -> String#each_char"
    Shugo Maeda <shugo / ruby-lang.org> wrote:

> 前田です。

> > そして上記のように、わたしは String#map や #select をよく使います。
> > 逆に、文字単位の map や select には全く魅力を感じません。
> 
> わたなべさんも指摘されているように、一行ごとのStringの配列を使うようにす
> れば、要求は満たされるのではないでしょうか。
> 
> メモリ上で行ごとの処理を行いたい局面が多いことには同意しますが、それを
> Stringで実現しなければならないとは思いません。

以下の理由から、文字列配列では代用になりません。

* StringIO が受け付けるのが文字列なので、
  文字列を基本にするほうが明らかに可用性が高い。

* String#to_a がいつまで使えるか信用ならない。

  String#each は文字単位なのが「自然」だと言うなら、#to_a
  は文字配列を返すのが「自然」だろうと予想せざるを得ない。
  従って String#to_a は信用できない。

* 行指向だからと言って行指向の操作しかできないのは不満。
  例えば配列では特定部分を非破壊的に操作するのは難しい。
  文字列ブロックの末尾 (最終行) を rstrip したりしたいのに。

つまり、基調として行配列指向インターフェイスを使うのですが、
文字列塊としてのインターフェイスや IO インターフェイスもやはり
必要なのです。そのためには頻繁に相互変換する必要があります。
現状では、そのうちもっとも interoperability が高いのが IO と
String の組合せで、文字列配列を中心とする解決法はそれに数段
劣ります。具体的には次の変換表を見てください。

                        (一行一要素の)
src\dst   String          Array               IO
-------+---------------------------------------------------
String |     -             ない*1         StringIO.new(str)
Array  | lines.join('')      -               ない*2
IO     | io.read        io.readlines           -

*1  上記の通り、String#to_a は信用していない
*2  StringIO.new(list.join('')) はツーステップのうえ文字列経由なので不可


このように、IO⇔文字列 はワンステップの相互変換が可能なのに、
IO⇔配列  配列⇔文字列 ではできません。ここがだめだと言ってます。
現状でここを補っているのが String#each (およびその Enumerable
拡張と String#to_a) なのですが、String#each が変化すると総崩れに
なりかねません。

つまり、わたしの懸念の核心は String#each 自体の変化にあるのでは
なく、芋蔓式に他の個所のユーザビリティが下がることです。逆に言えば、
その他の部分のユーザビリティの低下を押さえられる方策が同時に取ら
れるのであれば必ずしも String#each の仕様が変わるのには反対では
ありません。しかしそれをすべて「関係ない」として無視されてしまう
のでは、こちらとしては妥協するわけにいきません。


まとめと結論

わたしが懸念しているのは使いやすさ (具体的には String Array IO の
相互変換) が下がることであり、同程度以上に使いやすい手法へ移行する
のであれば妥協できます。しかし現在の議論はそれを全く考慮せずに進め
られようとしており、単なるダウングレードです。よって妥協できません。


また具体的な補強案としては以下を提案します。

(1) 文字列 → 行配列 強化策

* 文字列を行配列にするメソッド String#lines を新設する (名前はHaskellから)

* Enumerator を組み込みにする
* to_enum を短くする。例えば #by とか
* map_by, collect_by, select_by... を全部用意してしまう

(2) 配列 → IO 強化策

* 配列からワンステップで StringIO を作れるようにする。
  (いいメソッド名が思いつかない)

* せめて配列を行指向 IO として扱える (gets を提供する)
  ラッパーライブラリを標準添付する。もっとも単純には

  class ArrayInput
    def initialize(lines)
      @lines = lines
    end

    def gets
      @lines.shift
    end
  end

  という感じだが、非破壊的なほうが望ましい。

  (もっと抽象化すれば、外部イテレータに変換したいということ)

優先順位としては、最低限 String#lines は確保したいです。enumerator
が強化されればさらに心おきなく String#each を捨てられます。
(2) は放棄しても構いません。
-------------------------------------------------------------------
青木峰郎