Re: "sep".join(list) が気持ち悪い理由

みんなー、トラックバックって知ってるよねー!? 他人のとこに長いコメント書くくらいなら自分のブログに書こうぜ!
#隠れてコソコソ陰口叩かれるぐらいなら超長いコメントのほうがいいけどな!!

methaneさんのコメント:

えぇ、そうですね。だからスレッドのjoinをArrayクラスに入れるのはおかしいです。同じように文字列のjoinをArrayクラスに入れるのに違和感を覚えないのは何故でしょうか?

すでに書いているように、頻出するケースだから。それにオブジェクト指向であれば、メソッド名が同じでもクラスが異なれば違う仕様にすることはごく普通に行なわれることなので。

「join」というメソッドが、どのクラスでも同じようにしないとダメという感覚がわからないです。多態が必要な場合は同じinterfaceじゃないと困りますけど、Threadのjoin()と文字列のjoin()を同じinterfaceにする必要なんかこれっぽっちもない。

Pythonだって、int の % と str の % じゃ意味も使い方もまるで別ですよね? こんな基本的なところからして違うんだから、joinがThreadと文字列とで違ってても別にいいんじゃないでしょうか。

Pythonでは「optionalなものがreceiverになることへの違和感」よりも合理性や名前空間の方が優先されるだけです。

なんかまだ誤解されていそうなので何度も書きますけど、*Pythonにおいて* "sep".join(iter)という仕様が合理的であることは認めています。合理的であると認めることと、optionalなものがreceiverになることに違和感をもつことは、別に相反することではないですよね? それとも、「違和感をもつ」=「合理的でないと主張している」ことになります?

違います。配列という概念の中に要素数という概念があり、それは整数型なので全く不自然ではありません。

それに対して、 *文字列の* 連結という概念がArrayにあるのは、名前空間を大切にする文化から見ると不自然だと言っているのです。これが *任意の要素* の連結であれば全く問題ありませんが、 *任意の要素* の連結の仕方はその要素しか知らないし、そもそも連結できない要素も沢山あります。

これもなんか勘違いされてそうな。RubyのjoinがArrayクラスに属しているのは*名前空間的に*正しいなんて、だーれも主張していないですから。optionalなものがreceiverになるくらいなら、requiredなものがreceiverになるほうがよっぽど自然だとは思いますけど。

それから、名前空間うんぬんというなら、string.join(iter, sep)という関数形式がいちばん名前絵空間的には自然なんじゃないですかね。Pythonにおけるjoin()の戻り値の型は、sepだけで決定されるのではなく、sepとiter内の要素によって決まるんですから。double dispatch patternを使うなら別ですけど。
#これもすでに書いてますけど、string.join(iter, sep)という関数形式なら誰も違和感を持ちませんよ。sep.join(iter)だからいろんな人(Rubyに限らずPerlな人でも)が違和感を持つだけで。

iterの中身はstr型だという前提でjoinしているときに、間違ってstr以外が入っていたときに例外を出してほしい場合、str.join()が暗黙でstr()を要素に適用してしまうとチェックが面倒になります。

そうかなあ。
raise "error" unless array.all?{|x| x.is_a?(String) }
#こんなのが必要になるケースがわかんないけど。

あるいは、Pythonだとgetattr()とかdict.get()はエラーをだす・ださないをオプションで指定できますよね(厳密にはデフォルト値ですけど)。同じようにすればいいけじゃないかな。

逆に、数値とかも適当にstr()して文字列にしてほしい場合は、 str.joinが暗黙でstr()を適用しなくても、 sep.join(str(s) for s in iter) で簡単に変換できます。
要素に勝手にstr()しないのは、そういう合理性があるからです。

これ毎回やらなきゃいけないんですよね。*個人的には*、自動的に文字列にしてほしい場合のほうが多いから、頻出するケースのほうをデフォルトの動作にして、頻出しないケースのほうはオプションで指定できるようにするのが、いいAPIの設計だとは思いますが。まああくまで*個人的に*そう思うだけなので、そうは思わない人がいてもなんら不思議じゃないですけど。

あと、Pythonのstr.join()はJavaのStringBufferやRubyのString#<<()に匹敵する、文字列連結の基本中の基本メソッドなんだから、ループが2度走るのは好ましくないような。

Rubyはそういう文化ですよね。
Pythonでも関数のデフォルトパラメータなどで頻出するケースを優先しますが、名前空間に関しては違います。
「joinといえば一番よく使うのは文字列だよねー」という理由で *list* のjoinを文字列結合に割り当てたり、文字列結合専用のitertools.joinができたりはしません。

その通りですね。別にそれでいいんじゃないですか。誰も反対してませんし。

繰り返しになりますが、どちらがreceiverになるかよりも、名前空間の整合性、一貫性、合理性を重視しています。
最初に直感に反することがあったとしても、それが合理的な理由がわかればむしろそちらが自然だと思えるようになります。

繰り返しになりますが、*Pythonにおいて* "sep".join(iter) という仕様になっているのは合理的だと思います。Pythonにおいては合理的だと思います。(大事なので2回言いました。最初っから書いてることですけど。)
そのことと、optionalなものがreceiverになっていることに違和感を感じるのは、別に相反することでもなんでもなく、両立する事項です。
わかってもらえますか? これがわかってもらえないと、議論が堂々巡りになって終わらないんですけど。

あと、"sep".join(iter)が合理的な理由として「Pythonには文字列の種類が複数あるからそのうちの一つだけを特別扱いするのは不可能」ということを理由に挙げてますけど、あれはいくらなんでも取り消したほうがいいんじゃないかなあ。Pythonでのjoin()の戻り値の型は、separatorとlistの要素から決まることであってseparator単独で決まるものではないので、一つだけを特別扱いする必要なんかまるでないですよね。


#で、"sep".join(iter)が*Pythonでは*合理的であることをふまえた上で、optionalなものがreceiverであることにmethaneさんおよびPythonistaは違和感を持たないんでしょうか。すげー興味あります。