Haskell quasiquotation如何用于替换Haskell级别的令牌?

Dan*_*oni 15 dsl haskell metaprogramming

haskellwiki中描述的quasiquotation主要显示为在Haskell中嵌入其他语言的有用工具,而不会弄乱字符串引用.

问题是:对于Haskell本身来说,将现有的Haskell代码放入quasiquoter以便仅仅替换令牌并将结果传递给ghc是多么容易?也许模板Haskell在这里很关键?

我找了代码示例,但没有找到任何代码示例.一些EDSL可以通过减少其组合运算符的大小而受益于此功能(例如,将'a.|.b.>>.c'转换为'[myedsl | a | b >> c]').

sha*_*ang 18

您可以通过使用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
Run Code Online (Sandbox Code Playgroud)

这会更改变量expression(VarE),但不能对任何其他类型的表达式执行任何操作.

现在,走完整个AST并替换所有出现的>>我们将使用函数everywheremkTfrom Data.Generic.

import Data.Generics (everywhere, mkT)

replaceEveryOp :: Exp -> Exp
replaceEveryOp = everywhere (mkT replaceOp) 
Run Code Online (Sandbox Code Playgroud)

为了进行多次替换,我们可以更改函数,以便将任何运算符的关联列表替换.

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
Run Code Online (Sandbox Code Playgroud)

顺便说一句,这是一个很好的例子,通过使用视图模式语言扩展来编写更好的函数.

{-# 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
Run Code Online (Sandbox Code Playgroud)

现在我们要做的就是建立"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
Run Code Online (Sandbox Code Playgroud)

如果将上述内容保存到自己的模块(例如MyEDSL.hs),则可以导入它并使用准引号.

{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE QuasiQuotes #-}

import MyEDSL

foo = [myedsl| a || b >> c |]
Run Code Online (Sandbox Code Playgroud)

请注意,我使用的是||代替,|因为后者不是Haskell中的有效运算符(因为它是用于模式保护的语法元素).