如何在 Haskell 中编写模式准引号?
How can I write a pattern quasi quoter in Haskell?
我在编译时使用准引号来创建我的智能构造数据类型。这看起来像:
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
一切都符合第一个语句。
好的,我想我明白了。从基本字符串 s
开始,我可以将其包装在 StringL
和 LitP
中以获得文字字符串,因为 Text
的 IsString
实例将成为Text
。从那里我需要使用 ConP
:
应用 NonEmptyText
构造函数
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
是 Q Exp
?
我在编译时使用准引号来创建我的智能构造数据类型。这看起来像:
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
一切都符合第一个语句。
好的,我想我明白了。从基本字符串 s
开始,我可以将其包装在 StringL
和 LitP
中以获得文字字符串,因为 Text
的 IsString
实例将成为Text
。从那里我需要使用 ConP
:
NonEmptyText
构造函数
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
是 Q Exp
?