如何在Haskell中编写模式准引号?

时间:2019-02-21 02:27:45

标签: haskell template-haskell quasiquotes

我在编译时使用准引号创建智能构造的数据类型。看起来像这样:

import qualified Data.Text as T
import Language.Haskell.TH.Quote (QuasiQuoter(..))
import Language.Haskell.TH (Q, Exp, Pat(..), Lit(..))
import Language.Haskell.TH.Syntax (Lift(..))
import qualified Language.Haskell.TH.Syntax as TH
import Instances.TH.Lift () -- th-lift-instances package

newtype NonEmptyText = NonEmptyText Text

textIsWhitespace :: Text -> Bool
textIsWhitespace = T.all (== ' ')

mkNonEmptyText :: Text -> Maybe NonEmptyText
mkNonEmptyText t = if textIsWhitespace t then Nothing else (Just (NonEmptyText t))

compileNonEmptyText :: QuasiQuoter
compileNonEmptyText = QuasiQuoter
  { quoteExp = compileNonEmptyText'
  , quotePat = error "NonEmptyText is not supported as a pattern"
  , quoteDec = error "NonEmptyText is not supported at top-level"
  , quoteType = error "NonEmptyText is not supported as a type"
  }
  where
    compileNonEmptyText' :: String -> Q Exp
    compileNonEmptyText' s = case mkNonEmptyText (pack s) of
      Nothing -> fail $ "Invalid NonEmptyText: " ++ s
      Just txt -> [| txt |]

(如果需要,我可以提供一个独立的工作示例,我只是从更大的代码库中提取了该示例)

从本质上讲,只需为我的新类型派生Lift,就可以将数据类型放在表达式准引号[| txt |]中,以实现quoteExp

但是我在quotePat上遇到了麻烦。如果我这样做,例如:

Just txt -> [p| txt |]

然后,我得到一个警告,第一个txt未使用,第二个阴影第一个。我很确定该模式只是创建一个新名称txt,而不是像准引号一样,在范围内txt进行拼接,因为当我这样做时:

f :: NonEmptyText -> Bool
f [compileNonEmptyText|test|] = True
f _ = False

一切都与第一个语句匹配。

1 个答案:

答案 0 :(得分:1)

好的,我想我已经明白了。从基本字符串s开始,我可以将其包装在StringLLitP中,以获取文字字符串,由于Text的{​​{1}}实例,该字符串成为IsString。从那里,我需要使用Text应用NonEmptyText构造函数:

ConP

不幸的是,它比表达式版本冗长得多!我想知道是否会为compileNonEmptyTextPattern' :: String -> Q TH.Pat compileNonEmptyTextPattern' s = case mkNonEmptyText (pack s) of Nothing -> fail $ "Invalid NonEmptyText: " ++ s Just (NonEmptyText txt) -> pure $ ConP 'NonEmptyText [(LitP (StringL (T.unpack txt)))] 提供类型类,例如Q Pat是否为Lift