読者です 読者をやめる 読者になる 読者になる

小飼弾氏の基調講演を聞いて各言語の join と正規表現の実装についてなんかまあ考えた話 #pyconjp

Python

マサカリが飛び交ったPyCon2012 Day2 基調講演

講演を聞いたけど、いろいろ腑に落ちなかったりした。まあ各セッションとその講演についての感想は pyconjp2012 行ってきたよ各セッション感想 #pyconjp - atas に書いたのでそちらを参照してください。
404 Blog Not Found:my slides for #pyconjp
PyConJP 2012 2日目 基調講演 #PyConJP - Togetter
そのなかで少し気になった print と join と 正規表現についてちょっとだらだらと考えてしまった。ツッコミがあったら欲しいです。自分はちょっとJavaScript、メインはPythonだけどまだ1.5年程度、他の言語はまともにさわったことはないレベルです。

printについて

Python 3 から print は組み込み関数になる。今まで print hoge と文で表現できていたのが print(hoge) になる。2.x でも print(hoge) しても内容は変わらないけど、より言語の統一性を求めた形になった。で、それってどうなの?って質問が飛び交った。「まあどちらでもいい」とか「やっぱり print hoge のほうが楽」とか「printは組み込み関数として定義されているほうが統一的で良い」といろいろあるだろう。僕は最後の「Python の統一性を求めるために print(hoge)のほうが良い」と考えてる。実際今は 2.7 を使ってるけど基本的に print(hoge) で書くようにしている。

join について

join関数はリスト(配列)を引数にとってそれらを任意のセパレータを追加して文字列として返す関数。たいていどの言語にも実装されている(と思う)。例えばあるオブジェクトのプロパティをとって組み合わせて "musician/album/truck" とか "2012_09_16"とかを生成するときに使ったりする。で、それの言語実装について。
以下、コード内でコメントでかきます。なおネタ成分薄めてわかりやすく書きます。

# 今回のスライドにあったのはこれ
join "/" => qw/musician album truck/;
# ベタ書きっぽい書き方
join("/", "musician", "album", "truck");
# 配列を引数にとる書き方
my @ary = ("musician", "album", "truck");
join("/", @ary)

Perl の場合はセパレータを第2引数、配列を第2引数以降にとってやる方式。正直1文目の書き方はよくわからない

# クオートを省略した書き方
%w[musician album truck].join('/')
# 省略しない書き方
['musician', 'album', 'truck'].join('/')

Ruby は配列にjoin関数を生やしている。クオートを省略できたりするところがいかにも ruby らしいなぁとおもう。

// 特になにかがあるわけでもない
['musician', 'album', 'truck'].join('/');

JavaScript は配列にjoin関数を生やしている。まあJS触ってきたしそんなもんかなって思ってた。

# 当たり前だと思ってた
u"/".join(['musician', 'album', 'truck'])

Python は文字列にjoin関数を生やしている。そんなもんかなって思ってた。ってあれ?
join関数を配列に組み込むか文字列に組み込むか?どっちがいいんだろう?といってもまあもう実装されているし今更な話だけど、どっちのほうが「筋が通っている」んだろう?って考えちゃった。join関数は配列を引数にとって文字列にして返す。では、それは配列の関数であるべきか?文字列の関数であるべきか?
自分なりに考えたのは、どちらかというと文字列の関数であるPythonの書き方のほうが「筋が通っている」と思う。なぜか。考え方として「配列を扱ってよしなにする」というのは同じだけど、「配列"を"引数にとる"文字列を返す関数"なのだから、それは文字列の関数であるべきだ」「これは配列"そのものに対して操作する"関数ではない」からだ。なので、この部分では Python はいいなと思う。

正規表現の文字列置換について

まあスライドにおいて「魔女」という文字を Python において文字列を単に除去したいなら str.replace()でいい

# 特定文字列を空文字列に置換する
u"やがて生まれる魔女".replace("魔女", "")
# -> "やがて生まれる"

それはいいとして、正規表現だとどうするか

my $string = "やがて生まれる魔女"
$string =~ s/魔女//g;
print $string
# "やがて生まれる"

Perl の場合。まあ普通に正規表現でパターンから空文字に置換してる。 =~ で「含んでいる」という演算子があるくらいか。

"やがて生まれる魔女".gsub(/魔女/, "")
# "やがて生まれる"

Ruby の場合。これも同じ。ただ直接文字列から正規表現の関数を呼べること、あと gsub ってよくしらないけどたぶん s/pettern/string/g の g だよね。これもまた ruby っぽい感じする

"やがて生まれる魔女".replace(/魔女/g,'')
// "やがて生まれる"

JavaScript の場合。自分はJavaScriptからプログラミングにはいったのですごく自然だ。文字列の関数として呼べるし、replace関数に単に正規表現パターンを使えるというだけだ。すごい便利だと思ってるし、これが好きかな

# 正規表現モジュールのインポートが必要
import re
# スライドではこうしていた。
re.compile("魔女").sub("","やがて生まれる魔女")
# ただ、re.compile は正規表現パターン処理が重くなるから先にコンパイルしたいという話らしいので簡単なパターンならこう書ける
# ちなみに正規表現では r"" とつけることで \ エスケープ以外の諸々を無視して書きやすくできるからそうする
re.sub(r"魔女", "", "やがて生まれる魔女")
# "やがて生まれる"

これでいくと、PerlRubyJavaScriptのほうが「筋が通っている」と感じる。Python の sub ってなんかヘンじゃない?だって、普通は置換される文字列が先に来て、正規表現パターンの通りに置換される。vimも同じですね。でもPythonだと「re.sub(正規表現パターン, 置換文字列, 置換元データ)」って、逆になってる。これってなんかヘンな感じがする。なんでこうなっているんだろう?なにをどう考えたらこういう形になるだろう?
あと Python はじめたてのころにハマったのだけど、正規表現は import re して正規表現モジュールとしてつかわなきゃいけない、つまり文字列の関数にしなかったのはなぜだろう?まあ、これに関しては別にいいのだけど、どういう設計思想したら文字列組み込み関数ではなくわざわざ標準モジュールとしたのだろう。ちょっと考えられるのは正規表現パターンのコンパイルのためなのかなぁとか思うけど。うーん

まあそんな

最初に言ったとおりに言語には言語ごとの文化や考え方があるのだからいいのだけど、それにしてもなんでまたこうなっちゃったんだろうなぁって考えさせられた。僕はあまりにもまだまだ未熟だし余裕ないので Python/Django と JavaScript/jQuery でまあ少なくても1年くらいはやっていこうと思うけど、少しは落ち着いたら他の言語も最速文法以上のもうちょっと文化的なレイヤーを抑えられたらいいなぁ。