您可以构建操作 Haskell 代码的准引用器,例如,使用haskell-src-meta包。它将有效的 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 中的有效运算符(因为它是用于模式保护的句法元素)。