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

「Re: "sep".join(list) が気持ち悪い理由」
http://d.hatena.ne.jp/kwatch/20090716/1247717055

みんなー、トラックバックって知ってるよねー!? 他人のとこに長いコメント書くくらいなら自分のブログに書こうぜ!

#隠れてコソコソ陰口叩かれるぐらいなら超長いコメントのほうがいいけどな!!

そうですよね。せっかく返信していただいたのに、トラックバックがないので気づきませんでした。

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

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

連結という操作が連結対象の型に深く結びついているのに、入れ物のArrayのメソッドになるのは気持ち悪くないですか?という意味です。 Array.join() が sep.join() より短く書ける訳ではないのだから、「頻出するケースだから短く書けるようにしている」という訳ではない。「頻出するケースだから名前空間を汚しても良い」というのであれば、Pythonistaからみると気持ち悪いです。

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

ええ。 int の % は int.__mod__(), strの % は str.__mod__() というように、一番関連が深い型のメソッドになっています。同じように Thread の join は Thread型のメソッドに、 文字列の join は 文字列型のメソッドになっています。とても一貫性が取れていますね。

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

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

合理的であると納得すれば、違和感が無くなります。
あと、optionalをどういう意味で使われているのか判らないのですが、「デフォルト値を規定できる物」という意味ですか?それとも「無くても良い物」という意味ですか?
もし「無くても良い物」だから気持ち悪いというのであれば、空文字列があると認識すれば一気に違和感が無くなりますよ?視点をちょっと変えるだけで消える違和感をそんなに固持されるのはどうしてでしょうか?
逆に、デフォルト値を定義できるものだから気持ち悪いというのであれば、その感覚が私には理解できません。

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

string.join() という関数もあります。が、sep.join()の方が短く書けますよね?名前空間も汚しませんし、何も困りません。だから sep.join() を使うのです。

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

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

毎回はやりません。というか、滅多にやりません。個人的には、sep.join() で結合する対象は文字列であることが多いです。さらに言えば、デフォルトの動作は安全側に振ってくれた方がうれしいです。

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

''.join(str(x) for x in a) で引数に渡っているのはジェネレータなんで、文字列が要素の一時リストを作っているわけではありませんよ?
他にも、 Python2 では itertools.imap(str, a) と書けるし、 Python3 では map(str, a) と書けます。Python3が一番スマートですね。(Python2の map(str, a) はリストを作るのでスマートではありません)

繰り返しになりますが、*Pythonにおいて* "sep".join(iter) という仕様になっているのは合理的だと思います。Pythonにおいては合理的だと思います。(大事なので2回言いました。最初っから書いてることですけど。)

そのことと、optionalなものがreceiverになっていることに違和感を感じるのは、別に相反することでもなんでもなく、両立する事項です。

はい、 id:kwatch さんが 「optionalなものがreceiverになっていること」 には強い違和感を感じているのに、「文字列の連結がArrayのメソッドである」ことに大して違和感を感じていないことは理解しました。

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

recieverによって決まるのは、メソッドの戻り値の型ではなく、メソッドの動作(実装)です。
str.join(self, seq) の動作が、「seqにunicodeが入っている場合はunicodeで結果を返す」という物になっています。このような実装になっているのは、Pythonにおいて str と unicode の関係が str から unicode へと自動格上げされるようになっているからです。3 + 1.1 の結果がFloatなのと同じです。

Pythonの文字列はstrとunicodeだけではありません。標準にbytearrayもありますし、それ以外にも作れます。たとえば bytearray では、入力は bytes か bytearray で、出力はbytearray という動作になっています。

>>> b = bytearray(":")
>>> b.join("foo bar baz".split())
bytearray(b'foo:bar:baz')
>>> b.join(u"foo bar baz".split())
------------------------------------------------------------
Traceback (most recent call last):
  File "<ipython console>", line 1, in <module>
TypeError: can only join an iterable of bytes (item 0 has type 'unicode')

このように、各種文字列型がその型にとって自然な .join()メソッドを定義するのが、多種のiterableと多種の文字列型に対して一貫したシンプルな短い解だから、Pythonは sep.join() を選んだのです。

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

えぇ、視点を少し変えるだけで消える違和感なんて気になりません。

このブログに乗せているコードは引用を除き CC0 1.0 で提供します。