2008年10月24日金曜日

Haskell で正規表現 (2) =~ , Python・Ruby と比較しながら

以前試したように Haskell において正規表現を使いたい場合は、Text.Regex モジュールを利用する。(cf. Haskell で正規表現)

流れとしては、

  1. 「正規表現型の値」を生成 : mkRegex
  2. (1) を利用して一致する部分があるか確かめる : matchRegex 
import Text.Regex
main = do 
    print $ matchRegex (mkRegex "(piyo)") "123hogepiyofuga"
    print $ matchRegexAll (mkRegex "(piyo)") "123hogepiyofuga"

結果は、

Just ["piyo"]
Just ("123hoge","piyo","fuga",["piyo"])

ただし、matchRegex において Just で包んで返される値は、 mkRegex の正規表現で subexpression として指定 したもの。

 

Python と比較

Python でも同様の手順を踏む。

  1. compile
  2. match または search (cf. 4.2.2 マッチング vs 検索)
import re
print re.compile("piyo").match("123hogepiyofuga")
print re.compile("piyo").search("123hogepiyofuga")

結果、

None
<_sre.SRE_Match object at 0x025F6598>

 

search と match の違い

4.2.3 モジュール コンテンツ によると、

search(pattern, string[, flags])

string全体を走査して、…

match(pattern, string[, flags])

もし string の先頭で0 個以上の文字が正規表現 pattern と マッチすれば、…

Haskell の matchRegex は search に相当するということかな。

 

compile なし

上記は正規表現を使い回さないのであれば、compile せずに、

print re.match("piyo", "123hogepiyofuga")
print re.search("piyo","123hogepiyofuga")

 

matchRegexAll のように

Haskell の matchRegexAll と同じものを取得するには、4.2.5 MatchObject オブジェクト を使い、

str = "123hogepiyofuga"
m = re.search("(piyo)",str)
print str[:m.start()], m.group(1), str[m.end():], m.groups()

結果は、

123hoge piyo fuga ('piyo',)

 

Ruby と比較

Ruby で正規表現と言えばすぐに /正規表現/=~ “文字列” を思い出す。(cf. Regexp - Rubyリファレンスマニュアル) そういう書き方ができるから、テキスト処理をするとき Ruby は扱いやすいなぁ~と思った。組み込み変数 は忘れてしまうけれど。 ^^;

p /(piyo)/ =~ "123hogepiyofuga"
p $`, $1, $', $~.to_a

結果、

7
"123hoge"
"piyo"
"fuga"
["piyo", "piyo"]

せっかくなので、上記の組み込み変数だけでも覚えておこう。

`~’

Python でも =~ が使えるといいのに…。 (+_+)

 

Haskell でも =~

Text.Regex.Posix.Wrap

前回、A Haskell regular expression tutorial を読むのを見逃してた。(via Cookbook - HaskellWiki) Haskell でも Ruby と同じように =~ が使えるようだ。

定義されている場所は、Text.Regex の一つ下の階層に Text.Regex.Posix があり、そのまた下の Text.Regex.Posix.Wrap。どういうモジュールかと思って見てみると、

WrapPosix.hsc exports a wrapped version of the ffi imports.

FFIとは - はてなキーワード 

ある言語から他言語で作成したライブラリを呼び出すインターフェイスで、ラッパーを作らずに直接呼び出す事が特徴である。

…と言われても、何に対してどうなのか具体的によくわからなかった。 ^^;

 

=~ の型

それはさておき、 =~ を見ると、

(=~) :: (RegexMaker Regex CompOption ExecOption source, RegexContext Regex source1 target) => source1 -> source -> target

うわっ!(@_@;) この型クラスの制約はどうやって読めばいいのだろうか? QQQ

A Haskell regular expression tutorial によると、とりあえずはシンプルに考えておけと。

this function is polymorphic in both its arguments and its return type; … Here’s a simplified type signature, to give you the idea.

  (=~) :: string -> pattern -> result

(I’ve left out the constraints on these type variables. …

なるほど。しかし、引数だけではなく、返り値の型まで多相とは…?

 

使い方

とりあえず、上記にならって使い方を試してみる。

import Text.Regex
import Text.Regex.Posix
main = do 
  print $ ("123hogepiyofuga" =~ "(piyo)" :: Bool)       # True

最後の :: Bool と型を指定するのがポイント。

最初にマッチした文字列を返すには、

  print $ ("123hogepiyofuga" =~ "(piyo)" :: String)     # "piyo"

マッチした全ての文字列をリストにして返すには、

  print $ ("123hogepiyofuga" =~ "(piyo)" :: [String])    # ["piyo"]

マッチした部分よりも前、マッチした文字列、マッチした文字列の後ろを返したい場合は、

  print $ ("123hogepiyofuga" =~ "(piyo)" :: (String,String,String))   # ("123hoge","piyo","fuga")

上記に加えて、先ほどのように部分式にマッチした文字列をリストにしたものも加えたい場合は、

  print $ ("123hogepiyofuga" =~ "(piyo)" :: (String,String,String,[String]))  # ("123hoge","piyo","fuga",["piyo"])

マッチした文字列の位置とその長さを得たい場合は、Text.Regex.Base をインポートしておいた上で、

  print $ ("123hogepiyofuga" =~ "(piyo)" :: (MatchOffset,MatchLength))  # (7,4)