9

我正在编写一个简单的 HashString 类,它只是一个字符串及其哈希:

data HashString = HashString Int    -- ^ hash
                             T.Text -- ^ string!

现在我试图在编译时生成这些,例如:

$(hString "hello, world") :: HashString

我希望哈希和文本打包在编译时发生。我该怎么做呢?

到目前为止,这是我尝试过的,但我不确定它是否正确,也不确定它在编译时会做所有事情:

hString :: String -> Q Exp
hString s = [| HashString (hash $ T.pack s) (T.pack s) |]
4

1 回答 1

14

您编写代码的方式,在编译时不会发生评估。当您使用 引用 Haskell 表达式时[| ... |],引用的代码/AST 会插入到您应用它的位置,无需任何评估,因此编写:

$(hString "hello, world")

和写法完全一样:

let s = "hello, world" in HashString (hash $ T.pack s) (T.pack s)

但请这样想:您使用[| ... |]引用稍后插入的表达式,并在编译时使用$(...). 因此,如果您$(foo)在带引号的表达式中包含一些代码bla = [| bar $(foo) |],doing$(bla)将生成代码bar $(foo),而代码又将foo在编译时进行评估。此外,要获取您在编译时生成的值并从中生成表达式,请使用该lift函数。所以,你想要做的是:

import Data.String (fromString)
import Language.Haskell.TH.Syntax

hString s = [| HashString $(lift . hash . T.pack $ s) (fromString s) |]

这会在编译时评估散列函数,因为在解析外部拼接之后解析内部拼接。顺便说一句,使用fromStringfromData.String是从 a 构造某些OverloadedString数据类型的通用方法String

HashString此外,您应该考虑为您的界面制作一个准报价器。使用 quasi-quoters 比手动调用 splice 函数更自然(而且您已经使用过它们;无名[| ... |]引用器引用 Haskell 表达式)。

你会像这样创建一个准引用器:

import Language.Haskell.TH.Quote

hstr =
  QuasiQuoter
  { quoteExp = hString -- Convenient: You already have this function
  , quotePat = undefined
  , quoteType = undefined
  , quoteDec = undefined
  }

这将使您可以HashString使用以下语法编写 s :

{-# LANGUAGE QuasiQuotes #-}
myHashString = [hstr|hello, world|]
于 2012-02-11T21:26:00.550 回答