From: Shin-ichiro Hara Date: 1998-12-13T02:30:09+09:00 Subject: [ruby-dev:3793] Re: List() 原です。 > 前田です。 > 僕がいいたかったのは、原さんの仕様で、 > > class Enumerable > def each_with_index > i = 0 > each { |*x| > yield(*(x+i)) > i+=1 > } > end > end > > class Hash > def each > each_keys {|key| > yield([key, self[key]]) > end > end > end > > と定義した時に、each_with_indexの挙動は、Hashを書いた人の意図と > 異なるのではないかということです。 そこを私はそうは思わないです。で、この感じ方の差は [key, self[key]] を見たとき「あ、Array オブジェクトを送ろうとしている。」と思って しまう私と「あ、Array プロトコルで key, self[key] を送ろうとし ている」と感じる前田さんの差なんですね。これはArrayオブジェクトと Arrayプロトコルは全く同じ物であるという事実と yield(1, 2) が実は yield([1, 2]) と全く同じであるという事から、二つの矛盾が一つの円 環をなしているわけです。sqrt(z) の作るリーマン面の様に。ほんまか いな:-)。それで話がかみ合っていて同時にかみ合わない。(^^; > > > 少なくとも標準ライブラリについては、配列で複数の値を渡すことを > > > 期待したコードをすべて書き直す必要があるように思います。 > > > > 変だなあ。私は書き直す必要があるライブラリは殆どないと思うのです > > が、例えばどんな例がありますか?もちろんコンパイルし直す必要はあ > > るでしょうが。:-) > > 少なくとも、Hash#eachをyield(key, val)に書き直さないと、 > > module Enumerable > def each_with_index2 > i = 0 > each { |*x| > yield(*(x + i) > i += 1 > } > end > end > > が期待通りに動かないと思います。 そうです。 > これをレアケースだから問題ないとすると、そもそもモデル変更の > 必然性が疑われてしまいますよね? ここも同じ反論の繰り返しになりそう。ちょっと控えます。(^^; > > 問題:次の例のような動作するな Enumerable のメソッド > > each_with_index3 の定義は何? > > > > [1, 2].each_with_index3 { |x| #=> x = [1, 0] > > x = [2, 1] > > [[1], [2]].each_with_index3 { |x| #=> x = [[1], 0] > > x = [[2], 1] > > [[1,2], [3,4]].each_with_index3 { |x| #=> x = [[1,2], 0] > > x = [[3,4], 1] > >  {1=>2, 3=>4}.each_with_index3 { |x| #=> x = [1, 2, 0] > > x = [3, 4, 2] > > > > 私は、現在の仕様では、レシーバ self の形による場合分けでしか > > 書けないと思います。 > > 同意します。 > > 問題は実はyield(1, 2)とした場合ではなくて、yield(1) > とした場合なんですね。 > > yieldを本来のapply的な意味に限定すると、Array#eachは、 > > class Array > def each > for i in 0..self.length-1 > yield([self[i]]) > end > end > end > > と書いて、 > > [1, 2, 3].each { |x,| > p x > } > > とするべきなんですよね。 > def each_with_index > i = 0 > each { |x| > yield(x + i) > i += 1 > } > end > > という定義が正しい。 > > という答案にしておきます。 なるほどぅっ。問題の答えではないですが100点満点です。(^^) やっと前田さんの意図するところが理解できました。 yield に値を渡すときには単項であっても [] でくるむ訳ですね。 それを原則にすると。それはうまく行きますね。 > Array プロトコルで、yieldをapply的な意味に完全に限定した場合 > (つまりyield(i)をやめてyield([i])にして|i,|で受ける)は、より > 解像度が高いですよね。 > # 上記のeach_with_index/Hashのケースです。 > ただ実際には互換性の問題があるので、現状のArray プロトコル > (yield(i)を許す)を用いるより、List プロトコルを導入した方が > 解像度は高くなる、と。 前田さんの方法は、完全に Array プロトコルの上にすべてを載せる 訳ですね。配列 [1] を渡すにも yield [[1]] という具合に Array プロトコルの上に配列を載せるわけですね。これは List プロトコル と同じ解像度100%ですね。 > とりあえず、現状のyield=applyモデルではArray#eachの定義は、 > 例外的(なのにもっとも多く使われている(^^;)ケースなので考慮 > しなくてよい、したがって ふむふむ。そっちを例外とするわけですか。そういう気持ちなんです ね。そいつは筋が通っています。 それでは前田さんは結局どういう仕様が望ましいと考えているのです か?現実的な線で、すなわち、論理がすっきりしていること、互換性、 matz 氏の性格:-) などを総合的に考えて、ベストは何でしょうか。 (1)現状のまま これはこの前の言葉を使うと「解像度が低い」そして「それを克服する 手段はない」ということについては意見は一致していますよね。ただし 互換性はかなり:-)高いです。 解像度が低いといってもユーザーが(List() の様な)独自のプロトコ ルを設計すれば現状の上で100%の解像度を得ることは可能なのです が。 (2)現状のまま+若干の each の修正など 標準ライブラリでは先の前田さんの Array#each の様にオブジェクトを 単体で渡さないで [ ] でくるむように修正をする。ユーザーにも汎用性 のあるイテレータを定義したい場合は yield のパラメータにオブジェク ト単体を渡さず [ ] でラップするような設計を指導する。 (3)[ruby-dev:3704]の様に [ ] のラップを自動化する > yield(1) (args == [1]): > |x| => x = 1 > |x,| => x = 1 > |*x| => x = [1] > > yield(1, 2) (args == [1, 2]): > |x| => x = 1 > |x,| => x = 1 > |x,y| => x = 1; y = 2 > |*x| => x = [1, 2] > > yield([1, 2]) (args == [[1, 2]]): > |x| => x = [1, 2] > |x,| => x = [1, 2] > |x,y| => x = [1, 2]; y = nil > |*x| => x = [[1, 2]] > > つまり|x|を|x,|と同じ意味に、|*x|をこれまでの|x|の意味にするわけです。 これは非常にすっきりしているが、かなり非互換性がおおきい。 他にも考えられますが、現実化の可能性の大きいところで前田さんのいち 押しは何ですか? > P.S. ちなみにこんな時間まで起きてるのは卒論を来週先生に見せなければ、 > いけないからで、Rubyで遊んでたわけではないです:-( > # なのに、こんなメールを書いてていいのだろうか。 痛いほど状況がわかるなあ。(^^)