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: 正規表現は使い方に制約がなさすぎて嫌、という考え方もあるかと思います

19 comments:

  1. トレードオフといいつつ、正規表現よりな意見に読める(デメリットが少なく見える)のが気になりました。

    まず、正規表現リテラルを採用していても、 Ruby は Python と同等のメソッドを用意しています。既存の言語にリテラルを導入する場合は、同時に普通のメソッドを deprecate するという判断をしない限り、「引き換え」ではなく「両方」の学習が必要になります。
    (プログラムは、書く何十倍も読まなければならず、チーム開発でいちいちこの場合はこっちを使えと決めるのは難しい)

    プログラミング言語において、スタックマシンのバイトコードを直接書くよりも、それを抽象化した言語を使う大きな理由は可読性です。
    string.startswith(prefix) や word in string などは、コードレビューを行うときに意味を取りやすいですし、処理を組み合わせるのも慣れたプログラミング言語の構文が利用可能です。記号よりキーワードのほうが、慣れるまでのコストが低いのです。
    抽象化を捨てて、処理に寄せるということは、コード上から、「どう動いて欲しいか」という情報を外して「どう動くか」というコードにしてしまうことです。
    例えば、 /^abc/ とか /ab.*cd/ というコードを見た時に、改行文字がどう扱われるのかはわかりますが、どう扱いたかったのかはわかりません。
    これも、コードレビューのコストを増やす要因です。

    ReplyDelete
    Replies
    1. コメントありがとうございます。

      論点について、Prosが多いように見えるというのは列挙していったらそうなっただけなので、その点を批判されても答えかねます。Pros/Consの比較は、その個数ではなく、それぞれの重みに基づいて総合的に評価されるべきです。

      正規表現に依存しないAPIを提供するプログラミング言語において正規表現リテラルを同時に提供するのが良いかどうかという論点は、僕もあると思います。が、本稿は正規表現リテラルを導入することで得うるメリットについて述べているだけであって、どの言語においても必ずそのメリットが得られると主張しているわけではありません。その点はご理解いただければと思います。

      正規表現がabuseされる結果読みづらくなる可能性がある、というのは注記してあります。また、仕様に基づいて正規表現を使わずに処理を実装するのと、仕様に基づいた正規表現を実装するのとのどちらがTCOが安いかについて、前者が低コストであると一般化できるとは思いません。

      Delete
    2. 例えば、「この点については、以下のトレードオフが存在します。」以下がとても不公平に見えます。

      メソッドを使った場合はメソッドを覚えないといけないけど、正規表現にもたくさんの記号・フラグがありますよね?
      検索しやすさ、IDEの補完機能でメソッド名から意味を推測できるなどの面を無視して、「簡単に書くことができる」というのは暴論だと思います。

      Delete
    3. 速度の面でも、JSなどの例外があるために一般化できないのは正しいですが、多くの(特に静的型付けの)処理系では通常のメソッドのほうが速い、処理系を新規に作る場合でも最適化がしやすいという事に触れられてないのは、レアケースを強調し一般的なケースを無視しているので不公平に見えます。

      Delete
    4. 「トレードオフが存在」「正規表現リテラルを採用することで、そのプログラミング言語はより煩雑で、利用者にとって使いづらいものになってしまわないか」「プログラマに正規表現の学習を強いる」と書いています。

      また、例えば、sljitやRegenのようなJIT対応した正規表現ライブラリも存在するので、高速な正規表現処理を自前で実装するのが難しいという認識は正しくないと思います。

      Delete
    5. (Twitterをご覧になっていない方へ)その後 methane さんと議論した結果、補遺を追記してあります。

      Delete
  2. 本論については特に異論はありません。枝葉末節ではありますが、コンパイルによる性能劣化については、キャッシュによって再コンパイルを防ぐような最適化はよく知られているように思います。たとえばPythonはキャッシュしていますよね。その分の性能劣化はほとんど考えなくて良いかなと思っています。

    ReplyDelete
    Replies
    1. コメントありがとうございます。異論ありません。

      Delete
  3. 正規表現リテラルなしでも便利に使えるライブラリがあるかどうかがポイントでしょう。

    うまく設計されたライブラリなら、正規表現リテラルがなくても簡潔にかけます。また先のコメントにあるように、正規表現をコンパイルした結果をキャッシュするライブラリであれば、そこの性能は別に気にしなくてもいいです。そして、そのようなライブラリがあるからPythonでは正規表現リテラルの必要性が薄い。

    この記事ではJavaScriptを例に出すことで、正規表現リテラルが必要であるという結論を出しています。しかし上記のようなライブラリを標準では提供していないJavaScriptを持ち出しているため、Pythonに慣れた人からは『不公平に見え』るでしょう。

    わざと劣るものを見せておいて「だから正規表現リテラルは必要でしょ?」と言われても、説得力に欠けるよなあ、という感想でした。

    ReplyDelete
    Replies
    1. > うまく設計されたライブラリなら、正規表現リテラルがなくても簡潔にかけます

      まず、正規表現を使うべきか否か、という議論と、使っても良いと考える場合に正規表現リテラルを導入する是非に関する議論は別個の問題です。

      また、おっしゃるような一般解はありません。本稿では反例として「^\s*abc」をあげていましたが、補遺として文字列処理関数が優位な例と正規表現が優位な例を追記しましたので、あわせてご覧ください。

      > わざと劣るものを見せておいて「だから正規表現リテラルは必要でしょ?」と言われても、説得力に欠けるよなあ、という感想でした。

      「正規表現の学習を強いることと引き換えに、より単純で強力な処理系を提供」するという有力なアプローチが存在するというのが本稿の要旨です(「単純」とは文字列処理関数群でカバーできる要件が少なくてもよいという意味です)。

      JavaScriptはそのようなアプローチの例としてあげているものですし、本稿の主張は要旨のとおりなので、文字列処理関数群でカバーしている要件が少ない言語仕様を一概に「劣る」とする批判は的外れだと思います。少なくとも、記事の論旨をふまえた上での反論とは言えないのではないでしょうか。

      Delete
  4. This is great do you have a catologue if so I would love one to share with friends and family.
    ICC T20 World Cup 2016
    Australia vs India time table

    ReplyDelete
  5. http://cricketworldcup25.blogspot.com/

    ReplyDelete
  6. Friendship Day Images 2016
    Find Happy Friendship Day 2016 HD Images, Quotes, SMS, Greetings and Songs. Also Pictures and Wishes for Best Friends. Best Wallpaper, Wishes and Pictures you can find.



    Raksha Bandhan 2016 Quotes
    Find Raksha Bandhan 2016 Images, Quotes, Greetings and Songs. Also Pictures and Wishes for your Brother or Sister. Best Presents, Gifts Ideas and Quotes Poems.



    15 August Independence Day
    Happy Independence Day 15th of August 2016 images and pictures. Watch fireworks, events and celebrations Here

    ReplyDelete