スコープとマッチの罠
〔プログラムな?話〕 12:04 2 Comment ツイート
以前、後方参照やらの動作で悩んでいた問題 について、一定以上の理解を得た。
なんか長くなった気がするので気になる人は続きを読むで読んでください。
これは「スコープ」と呼ばれる動作が原因らしい。スコープとは? もちろん望遠鏡のことである。が、この場合「見える範囲」といった意訳がしっくりくるかもしれない。 プログラミングにおいてはとても重要な概念である。プログラミングに重要、ということは、 ソフトやOS、ひいてはパソコンにとって重要ということである。俺見落としてた。
では具体的にどういうものなのか。スコープには「動的スコープ」と「静的スコープ」があるが、 動的スコープは説明が難しいし今回はあまり関係ないようなので、 静的スコープ(レキシカルスコープ・lexical scope)だけ説明する。勘のいい読者なら 「動く望遠鏡と動かない望遠鏡がある、つまり見える範囲が動くものとそうでないものがある」 というところまで気づいていると思う。そのとおり。 レキシカルスコープとは、見える範囲が常に一定であることを指す。
例えば、大きく複雑なプログラムAがあるとして、その中にさらに小さくかつ単純なプログラム A年1組~A年10組がある。1組のプログラムは、さらに小さくかつ単純な1班~10班で 構成されている。プログラムとは往々にしてこのような階層構造(ツリー構造)になっている。
で、例えばA年5組が「5組の平均点は500点」という情報を持っていたとする。 A年5組のテストの平均点が500点であることは、1班~10班の誰もが知ることである。 しかし例えばここで8班の内部情報として「8班の吉田さんのバストはCカップ」という 情報を持っていたとする。8班の人間は、5組のテストの平均点が500点であることも、 吉田さんがCカップであることも知っている。しかし「8班の吉田さんはCカップ」 という情報は、他の班の班員は知り得ない。また5組全体としても知り得ない。
これがプログラミングにおけるレキシカルスコープ(の範囲)である。 これをコードにするとおおむねこういうことになる。
program A年生:
5組 {
my $平均点 = 500;
8班 {
my $吉田さんのバスト = Cカップ;
}
}
ちょっとめんどくさいだろうか。つまり5組プログラムの中における8班プログラムの 情報値は、それより外部のプログラムは取得できないようになっている。これは絶対である。 逆に、5組プログラムの情報値を、その下位プログラムは何の問題もなく取得できる。 このように、プログラムを階層構造にするだけでそのルール付けを決定できることから、 「構文スコープ」という呼ばれ方もする。らしい。
さて、次は「マッチ」についてである。まず先に言っておくと、近藤真彦のことではない。 マッチ違う。ここだけはおさえておかなくてはならない。 マッチとは、ある値が一定の条件内において一致するかどうか、という条件分岐を行う式である。 例えば「近藤マチャ彦」という単語があるとして、この単語の中に「マッチ」という語句が含まれているか、 そういった判定を行ったり、あるいは語句を置き換えることができる。
$A = "近藤マチャ彦"; #変数Aに「近藤マチャ彦」を代入
if ($A =~ /マッチ/) { #この括弧内の式が「マッチ式」と呼ばれる
print "あります!"; #あれば、「あります!」と出力
} else {
print "ありません!"; #なければ「ありません!」と出力
} #結果は「ありません!」
特にPerlは文字に強いプログラミング言語である(と思っている)。 例えば「近藤真彦」の「真」の字を探し出して「真」を「マチャ」に置き換えることなど造作もない。
$A = "近藤真彦";
$A =~ s/真/マチャ/; #ここがマッチ式。「真」を「マチャ」に置き換える
print $A; #結果は「近藤マチャ彦」
「正規表現」と呼ばれる、特殊な記号を用いた表現を利用することで、さらに広範な利用が可能だ。 この「正規表現」には様々な機能があるが、以下は無駄な単語の連続を削除することができる。
$コメント = "あらしだよーんああああああああああああしねしねしねしねしねしねしね";
# 荒らし書き込みがあったとする
$コメント =~ s/あ{5,}//g; #「あ」が5回以上連続で書かれていたら消去
$コメント =~ s/(しね){2,}//g; #「しね」が2回以上連続で書かれていたら消去
print $コメント; #結果は「あらしだよーん」
実際にはこんなピンポイントな荒らし対策がとれるわけないが、こういった用途に使うことも出来る。
で、正規表現には「マッチした単語を覚えておく」という機能がある。これが 以前に書いた「後方参照」あるいは「グループ化」と呼ばれるものだ。 マッチ式の中でカッコ書きされた内側のマッチ式に一致した単語は、特殊な変数域に記憶され、任意に呼び出すことができる。
$A = "近藤マチャ彦";
$A =~ /近藤(.*?)彦/; #「近藤」と「彦」の間の語句が記憶される。
$B = "$1$1"; #$1という特殊な変数に記憶されている。
print "$Bで~す!"; #結果は「マチャマチャで~す!」
マチャ彦も早変わりである。
で、
簡単に言うとこの「後方参照」の機能に俺の予期しない動作があった。 マッチを2回以上行ったとき、例えば1回目のマッチで一致し$1に単語が記憶されていて、 2回目のマッチで一致する語句がなかった場合、$1は空にならず1回目のマッチの結果が残っている。
$A = "近藤真彦";
$B = "新田純一";
$A = ~/近藤(*.?)彦/; #$1に「真」が入る
print "\$1の中身は「$1」\n"; #結果は「$1の中身は「真」」
$B = ~/近藤(*.?)彦/; #マッチしない
print "\$1の中身は「$1」\n"; #結果は「$1の中身は「真」」
そんなこんなでようやく俺は理解したのだった。以上終わり