Haskell で正規表現を使いたくなったとします。Haskell には正規表現のリテラルがないので、文字列リテラルで代用します。Haskell の正規表現では、メタ文字の多くがバックスラッシュを使わずに定義されています。そこで、文字列中にバックスラッシュを書くことはあまりありませんが、それでも書く必要に迫られることがあります。
たとえば、Window のファイル名の区切り文字であるバックスラッシュや、丸括弧にマッチさせたい場合は、たくさんのバックラッシュを書く必要があります。
> :m Text.Regex.Posix > "\\foo\\bar.c" =~ "\\\\foo\\\\bar\\.c" :: Bool → True > "(1+2)/(3+4)" =~ "\\([^)]+\\)" :: (Int,Int) → (0,5)
トムさんから、QuasiQuote を使えばバックスラッシュをなくせるのではないかというアイディアを頂いたので、実装してみました。
{-# LANGUAGE FlexibleContexts #-} module RegexLiteral where import Data.String import Language.Haskell.TH import Language.Haskell.TH.Quote import Text.Regex.Posix re :: QuasiQuoter re = QuasiQuoter parseExprExp parseExprPat parseExprExp :: String -> ExpQ parseExprExp x = return . LitE . StringL $ x parseExprPat :: String -> PatQ parseExprPat = undefined instance IsString Regex where fromString s = make s where make :: String -> Regex make = makeRegex
正規表現のリテラルは、ちょっと長いのですが、 "[$re|...|]" と表現します。
% ghci -XQuasiQuotes RegexLiteral.hs > "\\foo\\bar.c" =~ [$re|\\foo\\bar\.c|] :: Bool → True > "(1+2)/(3+4)" =~ [$re|\([^)]+\)|] :: (Int,Int) → (0,5)
ちなみに IsString のインスタンスにしているので、Regex を必要とするところにも、そのまま使えます。
% ghci -XQuasiQuotes -XOverloadedStrings RegexLiteral.hs > ([$re|\\(foo)\\(bar\.c)|] :: Regex) `matchOnceText` ("\\foo\\bar.c" :: String) → Just ("",array (0,2) [(0,("\\foo\\bar.c",(0,10))),(1,("foo",(1,3))),(2,("bar.c",(5,5)))],"") > ([$re|\([^)]+\)|] :: Regex) `matchAllText` ("(1+2)/(3+4)" :: String) → [array (0,0) [(0,("(1+2)",(0,5)))],array (0,0) [(0,("(3+4)",(6,5)))]]
対話的なこの例では、型を決めるために型の指定が面倒になっていますが、ソースに書けばこのような煩わしさはありません。
という訳で、fromString as eval プロジェクトはいけそうです! (謎)