问题描述
我正在实现一个基于使用标准 haskell 函数/组合器来构建数据库查询的 DSL。从实现 POV 中,我决定在查询中表示变量,如下所示:
newtype Variable = Var { fromVar :: Text }
然而,这迫使用户经常写 Var "something"
,所以我决定
编写一个自动执行此操作的 quasiquoter。
这是 DSL 的示例:
{-# LANGUAGE OverloadedStrings #-}
maxQuery :: Query MAX
maxQuery = match
( sch `isa` "school"
$ forWhich "ranking" `labelMatches` ran $ε)
`get` [ran]
`max` [ran]
where
[sch,ran] = map Var ["sch","ran"]
我希望它是什么:
maxQuery :: Query MAX
maxQuery = match
( sch `isa` "school"
$ forWhich "ranking" `labelMatches` ran $ε)
`get` [ran]
`max` [ran]
where [defVars| sch ran |]
或类似的东西。
我写的quasiquoter在这里:
{-# LANGUAGE TemplateHaskell #-}
module TypeDBTH where
import Language.Haskell.TH.Syntax
import Language.Haskell.TH.Quote
import Data.List.Split
import Data.Text (pack)
mkVars :: [String] -> Dec
mkVars vars = ValD
(ListP (map (VarP . mkName) vars))
(normalB (ListE (map (\v -> AppE (ConE $ mkName "Var")
$ AppE (VarE $ mkName "pack")
(LitE $ StringL v))
vars)))
[]
defVars :: QuasiQuoter
defVars = QuasiQuoter { quoteDec = quoteVars }
--,quoteExp = expQuoteVars }
quoteVars :: String -> Q [Dec]
quoteVars = return . return . mkVars . filter (/= "") . splitOn " "
expQuoteVars :: String -> Q Exp
expQuoteVars s = return $ LetE [(mkVars . filter (/= "") . splitOn " " $ s)] (LitE $ StringL "x")
本来我只写了quoteVars
。为了在 ghci 中进行测试,我添加了 expQuoteVars
。
但是,现在删除后一个并尝试编写
...
where [defVars| sch ran |]
给我留下两个错误:
lib/TypeDBQuery.hs:806:1: error:
parse error (possibly incorrect indentation or mismatched brackets)
因为 where [quasiquoter]
后面没有任何内容
和
lib/TypeDBQuery.hs:807:5: error:
• Exception when trying to run compile-time code:
lib/TypeDBTH.hs:18:11-46: Missing field in record construction quoteExp
Code: Language.Haskell.TH.Quote.quoteExp defVars " sch ran "
• In the quasi-quotation: [defVars| sch ran |]
|
807 | x = [defVars| sch ran |]
| ^^^^^^^^^^^^^^^^^^^^
我如何将准引用器用于 quoteDec
而不是 quoteExp
?
这可能吗?
如果这更容易的话,我也愿意像这样使用它:
maxQuery :: Query MAX
maxQuery = let [defVars | sch ran |] in
$ match
( sch `isa` "school"
$ forWhich "ranking" `labelMatches` ran $ε)
`get` [ran]
`max` [ran]
我查看了 wiki.haskell.org 和 TH 模块的“教程”和信息站点,但不知道如何执行此操作... https://wiki.haskell.org/Template_Haskell#What_to_do_when_you_can.27t_splice_that_there https://wiki.haskell.org/Quasiquotation https://wiki.haskell.org/A_practical_Template_Haskell_Tutorial
解决方法
不幸的是,您只能在顶级声明中使用声明准引号。来自the documentation:
准引号可能会代替
- 表达
- 一种模式
- 一种类型
- 顶级声明
您可以考虑使用 OverloadedStrings
代替 TH:
instance IsString Variable where
fromString str = Var (pack str)
maxQuery :: Query MAX
maxQuery = match
( "sch" `isa` "school"
$ forWhich "ranking" `labelMatches` "ran" $ε)
`get` ["ran"]
`max` ["ran"]