Wednesday, December 18, 2013

プログラミング言語における正規表現リテラルの必要性について

Twitterに書いたことのまとめです。

プログラミング言語の仕様の一部として正規表現リテラルを提供することの得失について、JavaScriptを例に説明します。

■より簡潔なコード

言うまでもありませんが、正規表現リテラルを使った方が簡潔なコードになります。
(new RegExp("abc")).exec(s)  // リテラルを使わない場合
/abc/.exec(s)                // リテラルを使った場合
また、正規表現リテラルがない場合は、文字列リテラルとしてのエスケープと正規表現としてのエスケープが二重に必要になる結果、コードの保守性が低下します注1
new RegExp("\\\\n");  // リテラルを使わない場合
/\\n/                 // リテラルを使った場合

■エラー検出タイミング

正規表現リテラルがない場合、実際にその正規表現が評価されるまで記述エラーを検出することができません。正規表現リテラルがあれば、コンパイル時注2にエラーを検出できるので、開発効率と品質が向上します。
new RegExp("(abc")  // 実行時例外
/(abc/              // コンパイル(起動)時にエラー検出

■実行速度

正規表現リテラルがないと、正規表現を適用する度ごとに、毎回正規表現をコンパイルするコードを書きがちです。これは、実行速度を大幅に悪化させます。正規表現リテラルがあれば、正規表現を言語処理系側でコンパイルして使い回すことができるので、実行速度が向上します。
new RegExp("abc").exec(s) // 実行する度に正規表現がコンパイルされる
/abc/.exec(s)             // 正規表現のコンパイルは実行回数に関係なく1回

また、正規表現に対しては「単純な文字列処理関数より遅そう」という意見を目にすることもありますが、そのような一般化は誤りです注3。例えば、JavaScript処理系における速度比較についてはregexp-indexof・jsPerfをご覧ください。ウェブブラウザによっては、正規表現を使ったほうがString#indexOfよりも高速なのがご確認いただけると思います。

■より単純で強力な文字列API

上記3点より、正規表現の使用を前提とするのであれば、正規表現リテラルを採用した方が言語処理系の利用者の開発効率が向上することは明らかだと思います。

残る問題は、正規表現リテラルを採用することで、そのプログラミング言語はより煩雑で、利用者にとって使いづらいものになってしまわないかという点です。

この点については、以下のトレードオフが存在します。

PythonやPHPのような正規表現の使用を積極的にアフォードしていないプログラミング言語では、多くの文字列処理関数が存在します。利用者は、これらの関数の仕様を記憶するか、あるいは都度ドキュメントを参照することを求められます。

これに対し、JavaScriptやPerlのような正規表現リテラルを提供しているプログラミング言語では、文字列処理関数の数は比較的少なくなっています。必要な処理は、プログラマが正規表現を使って簡単に書くことができるからです。

また、正規表現を使うことで、例えば以下のような、複数の評価手法を合成した処理を簡単に記述することができます。文字列処理関数を使うアプローチの場合、このような処理をするためには複数の関数を組み合わせざるを得ません。
/^\s*abc/   // 先頭に空白、続いてabc

■まとめ

以上のように、正規表現リテラルを言語仕様に導入すれば、プログラマに正規表現の学習を強いることと引き換えに、より単純で強力な処理系を提供することができます注4

言語処理系の開発時に正規表現リテラルを採用すべきか否かについて検討すべき論点は、だいたい以上のとおりだと思います。あとは、言語処理系がどのような目的で、どのような開発者に使われるのか、処理系開発者のバランス感覚によって決まる問題ではないでしょうか。


■補遺 (2013/12/19追記):

正規表現リテラル導入の是非は上のとおりですが、文字列処理に正規表現を推奨すべき否か、という論点について、私の考えを補足します。

プログラミング言語仕様の設計という視座にたった場合、文字列処理の手法として、文字列処理関数を推奨するアプローチと正規表現を推奨するアプローチのいずれがより優れているか、という問いに対して一般化できる答えは存在しません

たとえば、PHP等に存在するtrim関数(文字列の前後の空白を削除)は、同等の正規表現(s.replace(/^\s*(.*)\s*$/, "$1"))よりも簡潔です。文字列処理のうち、頻出するパターンについて、適切な名前をもつ関数を提供することで可読性を向上させるというアプローチは理に適っています。

逆のケースとしては、次のようなものがあります。

文字列処理を実装していると、文字列の末尾から改行文字を1つ(のみ)削除したいこともあれば、末尾の改行文字を全て削除したいこともあります。正規表現を使えば、この種の需要に対応することは簡単です(/\n$/ あるいは /\n*$/)が、これらの需要に対応する文字列処理関数をいちいち言語処理系で提供することは現実的ではありません。

あるいは、電話番号を入力するフォームにおいて適切な文字のみが使われているかチェックするのに適しているのは、文字列関数ではなく正規表現(/^[0-9\-]+$/)でしょう。

このように、正規表現を使うアプローチには、文字列処理関数と比較して、単純な処理においては可読性が劣りがちな一方で、様々なバリエーションがある処理を統一的な手法で記述できるという点では優れているという特徴があります。

注1: この問題は、エスケープシーケンスの衝突を回避できる言語(例: Perl)、raw文字列リテラルが存在する言語(例: Python)では問題になりません
注2: 多くのインタプリタ型処理系の場合は起動時
注3: プログラムのソースコードであれ正規表現であれ、コンパイル後にVMで(場合によっては機械語にコンパイルして)実行される以上、速度差はその変換処理の優劣の問題だと理解すべきです
注4: 正規表現は使い方に制約がなさすぎて嫌、という考え方もあるかと思います

Saturday, December 7, 2013

JavaScriptで高速なコードを書く際の注意点。または私は如何にして心配するのを止めてJSXを作ることにしたか

本日、福岡で開催されたプログラミング言語のパフォーマンスを考えるイベント「ぷろぐぱ」で、「JSX 速さの秘密 - 高速なJavaScriptを書く方法」という演題で講演しました。

JavaScriptで速いコードを書こうとする際に陥りがちな罠を紹介し、それらの問題にJSXではどうやって対処しているか、プログラミング言語設計と最適化機能の実装を説明しました。プログラミング言語設計に興味がある方にとっても、JavaScriptを使ったプログラミングに興味がある方にとっても面白い内容になっているかと思います。


講演中の写真とあわせてご笑覧いただければ。



※本記事はJSX Advent Calendar 2013の一部です。

Monday, December 2, 2013

Benchmarking asm.js using real-world code (zlib.js)

In response to a chatter between Japanese developers on Twitter about how asm.js would perform on web browsers other than Firefox, @y_imaya has written a benchmark between zlib.js that he develops and maintains vs. zlib-asm written by @ukyo by inflating (e.g. decompressing) Canterbury Corpus (a well-known set of files used for benchmarking compressor/decompressors).

His blog post can be found here but I could not hesitate to take my own benchmark on mobile devices since I use zlib.js at work, and may want to switch to zlib-asm if it runs faster than that.

So here are the numbers (his benchmark is ready to run here). The numbers on the charts show the milliseconds that took for decompressing the whole corpus.


First of all, asm.js running on Firefox is clearly the performance winner among all, showing the technical advantage of the approach. On the other hand, the asm.js-version is roughly 2x slower on other web browsers compared to the hand-written JavaScript variant. So for the time being, it seems that using a well-written JavaScript library would be preferable on browsers other than Firefox.

Anyways qudos to @y_imaya and @ukyo for their great work. I hope to see more benchmarks based on code that is actually used, so that we could understand more about the benefits of asm.js.

EDIT: It would be great if someone could add a benchmark of PNaCl running on Chrome as well.