methaneのブログ

このブログに乗せているサンプルコードはすべてNYSLです。

','.join() がなぜキモイのか

Ruby厨とPython厨が平行線の議論をしていたので、まとめてみる。

オブジェクト指向的にキモイ?

str.join() 処理での登場人物は2人いる。連結文字(区切り文字=separator)、連結される文字列の列だ。
この二つを比べると、「連結される文字列の列」が情報的に重要な場合がほとんどだろう。それを元に文字列の列が主役で連結文字はオマケと考えると、「joinが主役でない連結文字側のメソッドになる何てキモチワルイ」となる。
でも、別の視点で「連結する側とされる側」というように分類すると、「区切り文字 join 連結される文字」が素直な能動態で、「連結される文字列 (is) join(ed by) 連結される文字」だと無理やりな受動態になるので、''.join() の方が素直だ。

Rubyの場合は「配列が要素をjoinする」と配列が主体となっているので、後者の考え方はしにくい。なので 「''.join() がキモイ」と思ってしまう人が多い。

型的にキモイ?

今度は、join() が「文字列の列を連結」として取るか、「配列の要素を連結」と取るかの違い。
Pythonにとっては、文字列の列の連結。配列(list)以外でもiterableなものなら何でも連結される。str->unicodeのような暗黙の型変換はしても、数値→文字列のような明示的な型変換はされない。
それに対して、Rubyの場合は「配列の要素の連結」で、 Array.to_s のより柔軟なバージョン的な存在。連結する際には配列の要素を勝手に to_s する。
RubyのArray.joinが文字列専門になるのは「文字列はよく使うから特別」という発想だけど、Pythonにとっては「明確なメリットが無い限り特別を持ち込むのはキモイ」だろう。しかも、Pythonには文字列が埋め込み型だけで str, unicode, bytes, bytearray の4つ、(Python2.xではstrとbytesがエイリアスで、Py3kではstrがunicodeになってunicodeはなくなるので実質3つ)、そのうち一つだけを「特別」扱いするのはそもそも不可能だ。

名前空間的にキモイ?

効率的な文字列の連結を実装するには、文字列の内部表現を知る必要がある。それに対して、文字列の列の入れ物 (Array等) に関する詳細は知らなくても良い。
それだけ文字列べったりの join メソッドが Array のメソッドになっているのは、名前空間を大事にするPythonistaから見るとキモイ。
でも、Rubyist名前空間に関して寛容だ。 join の実装なんて気にせず、 sep.join より array.join の方が直感的だと判断したら躊躇せず array に join をぶち込む。 Pythonista から見るとキモイけど、Rubyistには何がキモイのか判らない。

Array.joinがキモイ?

もう一つ、Pythonだと iterable なら何でも join できるのに、Rubyだと Array以外の Enumerable は一旦 Array にしてやらないと join できない。
これはパッと見効率が悪そうだけど、実はPythonの ''.join も受け取った iterable がタプルやリストでない場合は一旦タプルにしてから処理している。(stringobject.c の string_join を見よ)
文字列は内部ではシンプルに文字型がメモリ上で連続している実装なので、そのメモリを確保する為にはまず連結後のサイズを計算したい。すると処理は 2-pass になり、iterableだと一回列挙した値をもう一回列挙できるかどうか判らないので、一旦シーケンス型にしてやる必要が出てくるわけだ。なので、リストより軽量なタプルが存在しないRubyでは、Arrayしかjoinできなくてもパフォーマンス上の制約にはならない。

パフォーマンスの問題が無いとすると、あとは使い勝手の問題だ。Pythonの場合、簡単にiteratorを作るためのyield文が用意されており、メモリ使用量を気にして頻繁に iterator を使う。また、シーケンス型かつコンテナ型な埋め込み型が一つでは無い。なので、リストを特別扱いする文化が無い。列の汎用型は iterator だ。だから join も iterator を受け取る。
それに対して、Rubyだとメモリ効率はあまり気にせずバンバン一時Arrayを作る。Arrayのメソッドチェーンを作ってキモチイイと悦ぶ。RubyにとってArrayは特別であり、列の汎用型は Array だ。Array以外をjoinしたかったら、 to_a.join すれば良い。
この文化の違いはお互いに理解しにくく、Pythonista から見ると「Arrayしかjoinできないなんてキモイ」し、Rubyistから見ると「Arrayで何が悪い」になってやはり結論はでない。

結論

結局のところ、文化の違いでしかないという結論に落ち着く。こんな結論の為になに長文書いてるんだろうね、俺は。