「正規表現がわからない」という人は結構多い。regexp)の習得は特に苦もなく、長大な正規表現もすんなり書けるようになったのであまり実感はないのだが、どうも正規表現の記述に関しては得手不得手がはっきり出る部分であるようだ。私が正規表現を教えてきた感覚で言うと、正規表現はむしろプログラミングに関してある程度知識がある人のほうがハマりやすい。プログラミングが関数型言語を除けば手続き的であるのに対し、正規表現は宣言的であるため、その切り替えができず、概念がごっちゃになる部分が大きいようだ。正規表現を理解するには、正規表現が宣言的であり、どのような概念の集合なのかということを理解するのが重要だ。それだけを理解すれば単純な要素の複合であるため、全く難しくはない。Yokohamaのクラス2授業であるプログラミング系科目で教えている正規表現の内容から、特に肝となる部分のエッセンスを抽出し、紹介したいと思う。察しのいい、定義によって明らかにされれば論理的導出で理解できる人にとってはこの内容が十分助けになるだろう。Yokohamaを頼って欲しい。EREの場合は「文字列」がない。RegexやOniguruma/Onigmoの場合、「パターン」が追加される。「条件」や「実行」や「演算」が追加されたりもするのだけど、そこは置いておく。正規表現わからないと言っている人の話題はそこではないだろうから。文字は正規表現の基本要素であり、正規表現の考え方としては文字が並ぶことで構文となる、と考えられている。正規表現は文法の条件の記述であり、文字というのは固定の文字ということではなく、条件に合致する文字を示すものである。通常の文字、例えばaは、aという文字だけが条件に合致する文字があることを示す。cのいずれかが条件に合致する文字であるということだ。これは例えば文字クラスやUnicodeスクリプトにおいても、どのような文字が条件に合致するかということを示しているだけであり、文字には変わりない。グループグループは文字条件を1文字以外に拡張するものである。任意の文字列が条件に当てはまることになる。グループ(パターン)の反復を示す。単純に「〜がn回あって」という話だと考えていい。場合によっては「n回またはm回」であったり、「n回以上」であったり、「n回以下」であったり、「n回からm回」であったりする。文字列はキャプチャによって得られる既知の文字列である。BREにおいて\(ab\)\1というと、ababということになる。文字列はパターンでなく、あくまで文字列なので、\(a[ab]\)\1というと、ababやaaaaにはマッチするが、aaabにはマッチしない。多くの人が理解していない部分として、文字列は量指定子の対象である。'\(a[ab]\)\1*'ababababababという結果になる。位置は「ここがこういう場所である」ということを条件とするものである。普通のBREの場合、「行頭」(^)と「行末」($)の2種類しかない。grepだと「単語協会」(\b)もあったりする。Perlの正規表現にあるのは位置をパターンで表すことができる幅ゼロアサーションだ。幅ゼロアサーションはパターンであるために混乱を招くかもしれないが、「...という場所」ということを述べていると理解すれば難しくない。(強敵)さて、最大の強敵がパターンだ。Perlだと5.14から追加され、Onigurumaにも目玉機能として入っている。正規表現そのものがパターンだから、パターンの中にパターンを表現するということは、「再利用」や「再帰」ができるということである。再帰だと混乱する人が多いので再利用について述べると、例えばnnn-ACnnn-Xnnn-nという文法が存在するとしよう。これは、\d{3}-AC\d{3}-X\d{3}-\dで表現できるものである。パターンを正規表現中で利用すること自体は、「グループの量指定」によって可能である。ところが、これはあくまで「グループで示されるパターンが連続する」場合であり、「グループで示されるパターンが断続する」場合や、「グループで示されるパターンが内包される」場合には利用できない。これを可能にするのが部分式呼び出しである。これを使うとOnigmoでは(\d{3})-AC\g<1>-X\g<1>-\dと書くことができる。\g<1>というところが1番目にキャプチャされたパターン(\d{3})を意味する。ここでは\d{3}なんていうものすごく単純なパターンであり、別に嬉しくないように思うが、これはものすごく複雑なパターンが繰り返す場合には相当タイピングを節約できる。Onigmoでは名前付き捕獲式を使うことができる。(?<num>\d{3})-AC\g<num>-X\g<num>-\d\gと\kで悩む人もいるが、\kは単純に捕獲された文字列である。これは既にあったものだ。abaaaaaである。([ab]\k<1>)は([ab])\1に等しい。abaaaabであり、これは結果的には([ab]){2}と書いても同じである。その場合は間に何かをはさむことはできないが。パターンが利用できる正規表現エンジンにおいては、「文字列の再利用」だけでなく「パターンの再利用」が可能になっている。結果的にグループが「パターンの宣言的定義」にもなっているのだ。正規表現は文法を示すものである、ということを忘れてはいけない。「Xはこのような文法を持っている」ということを記述するものである。正直なところ、BNFなんかよりは遥かにわかりやすく、簡単だと思う。ウェブアドレスの場合、httpまたはhttpsスキームを持つわけだが、そのあとはあまり変わらない。https?://(?:\g<user>:\g<password>@)?\g<domain>/\g<path>(?:\g<param>(?:\g<param>)*)?のように書くことができる。「一致する文字列」というふうに考え出すとドツボにハマる。正規表現はその概念を定義するものだ。プログラミング上では、値よりもクラスの定義のほうが近いと思えば良いだろう。クラスの定義は「Xというクラスは以下のようなものである。正規表現もまた「re1は以下のように正規表現によって表すことができるものである」と述べているわけだから。ちなみに、この場合では各捕獲式がこの正規表現中で定義されていないため、正規表現のコンパイルエラーになる。パターンに名前をつけられるのであれば、事前に部分的文法を定義しておきたいものだ。Perl6のRulesはそのようになっており、「部分式呼び出し」を中核に据えたものになっている。わかりやすく宣言的だ。