通常,使用 Template Haskell 时,绑定和数据构造函数的名称通过在它们前面加上单引号来引用:
showName, justName :: Name
showName = 'show
justName = 'Just
不幸的是,这对于第二个字符是单引号的名称不起作用,因为两个单引号之间有一个字符会被解释为字符文字。我该如何解决这个问题?
编辑:似乎用户指南是错误的关于没有逃生机制!您只需在最初的单引号后添加一个空格即可。所以...不要使用下面的技巧。
可以使用表达式引用和虚假的
Quote
实例来解决此限制。
{-# LANGUAGE DerivingVia #-}
module ExtractName (extractName) where
import Data.Functor.Identity
import GHC.Stack
import Language.Haskell.TH.Syntax
newtype Id a = Id {unId :: a}
deriving (Functor, Applicative, Monad) via Identity
extractName :: HasCallStack => Id Exp -> Name
extractName m = case unId m of
VarE x -> x
ConE x -> x
_ -> withFrozenCallStack $ error extractNameError
-- This is bogus, but good enough for what we're doing.
instance Quote Id where
newName _ = withFrozenCallStack $ error extractNameError
extractNameError :: String
extractNameError =
"extractName: the argument must be an expression quote containing a\n"
++ "single bare name, such as [| f'1 |]"
现在你可以写,例如,
f' :: Int
data Foo = B'ar
f'Name, b'arName :: Name
f'Name = extractName [| f' |]
b'arName = extractName [| B'ar |]
这是如何运作的?表达式引用将在实现
Exp
的任意 monad 中生成 Quote
。一般来说,对表达式引号进行脱糖可能需要 newName
方法,以对 let
和 lambda 表达式等进行脱糖。但是,它不需要需要newName
来对普通的旧绑定或数据构造函数进行脱糖。因此,我们可以为类似 Quote
的类型编写一个虚假的 Identity
实现,该实现适用于我们需要的各种带引号的表达式。一旦我们解开表达式,我们就可以从中提取名称。