たとえば、haskell-src-metaパッケージを使用して、Haskellコードを操作する準クォーターを作成できます。有効なHaskellコードをASTに解析し、それを変更できます。
この場合、ASTを変更する最も簡単な方法は、Data.Genericsを使用して、演算子を他の演算子に置き換えるAST全体にジェネリック変換を適用することです。
まず、一般的なHaskell式の変換関数を作成します。式を表すデータ型は、template-haskellパッケージのExpです。
たとえば、演算子>>
をに変換するには.>>.
、次のような関数を使用します
import Language.Haskell.TH (Exp(..), mkName)
replaceOp :: Exp -> Exp
replaceOp (VarE n) | n == mkName ">>" = VarE (mkName ".>>.")
replaceOp e = e
これにより、変数式(VarE
)が変更されますが、他の種類の式には何もできません。
ここで、AST全体をウォークし、すべての出現箇所を置き換えるために>>
、関数everywhere
とmkT
fromを使用しますData.Generic
。
import Data.Generics (everywhere, mkT)
replaceEveryOp :: Exp -> Exp
replaceEveryOp = everywhere (mkT replaceOp)
いくつかの置換を行うために、置換する演算子の関連付けリストを取得するように関数を変更できます。
type Replacements = [(String, String)]
replaceOps :: Replacements -> Exp -> Exp
replaceOps reps = everywhere (mkT f) where
f e@(VarE n) = case rep of
Just n' -> VarE (mkName n')
_ -> e
where rep = lookup (show n) reps
f e = e
ちなみに、これは、ビューパターン言語拡張機能を使用して記述した方がはるかに優れた関数の良い例です。
{-# LANGUAGE ViewPatterns #-}
replaceOps :: Replacements -> Exp -> Exp
replaceOps reps = everywhere (mkT f) where
f (VarE (replace -> Just n')) = VarE (mkName n')
f e = e
replace n = lookup (show n) reps
あとは、「myedsl」準クォーターを作成するだけです。
{-# LANGUAGE ViewPatterns #-}
import Data.Generics (everywhere, mkT)
import Language.Haskell.Meta.Parse (parseExp)
import Language.Haskell.TH (Exp(..), mkName, ExpQ)
import Language.Haskell.TH.Quote (QuasiQuoter(..))
type Replacements = [(String, String)]
replacements :: Replacements
replacements =
[ ("||", ".|.")
, (">>", ".>>.")
]
myedls = QuasiQuoter
{ quoteExp = replaceOpsQ
, quotePat = undefined
, quoteType = undefined
, quoteDec = undefined
}
replaceOpsQ :: String -> ExpQ
replaceOpsQ s = case parseExp s of
Right e -> return $ replaceOps replacements e
Left err -> fail err
replaceOps :: Replacements -> Exp -> Exp
replaceOps reps = everywhere (mkT f) where
f (VarE (replace -> Just n')) = VarE (mkName n')
f e = e
replace n = lookup (show n) reps
上記を独自のモジュール(例MyEDSL.hs
)に保存すると、それをインポートして準クォーターを使用できます。
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE QuasiQuotes #-}
import MyEDSL
foo = [myedsl| a || b >> c |]
後者はHaskellでは有効な演算子ではないため(パターンガードに使用される構文要素であるため)、||
代わりに使用したことに注意してください。|