在模板中执行类型相等 haskell
Performing type equality in template haskell
我在 Template Haskell
中有一个函数可以提取记录构造函数总和的类型信息,如下所示:
listFields :: Name -> Q ([[(String,Name,Type)]])
listFields name = do
TyConI (DataD _ _ _ cons _) <- reify name
let showClause (RecC conName fields) = (map (\(x,_,t) -> (nameBase $ x,x,t)) fields)
return $ map showClause cons
给定其中的字段类型,您如何比较该类型与 GHC.Base.String
或 Data.Text.Internal.Text
等特定类型的相等性?我在 TH
文档中看到 TypeQ
。它构建类型表达式。但是,我找不到任何关于如何构建特定类型(如 String
或 Text
或 Int
以便我可以将其用于相等比较的文档?将感谢有关如何执行此操作的指示,尤其是如何获取特定类型的 AST。
这个问题的原因是给定的记录构造函数,我们想将每个字段转换为Text
。但是,对于 String
和 Text
类型,show
和 pack
应该有不同的调用方式。因此,如果类型是Text
(不转换)或String
(只调用pack
,不调用show
)或其他类型(调用pack . show
假设存在 Show
个实例)。
根据评论中 jozefg
的建议,我通过使用具有类型签名 a -> Text
的重载函数解决了这个问题。再开几天看看有没有人有更好的建议。
这是我原来的TH
拼接(ghci
输出):
> runQ [| pack . show $ 1 ::Int|]
SigE (InfixE (Just (InfixE (Just (VarE Data.Text.pack)) (VarE GHC.Base..)
(Just (VarE GHC.Show.show)))) (VarE GHC.Base.$) (Just (LitE (IntegerL 1))))
(ConT GHC.Types.Int)
Int
转换为 Text
。但是,String
或 Text
上的 运行 pack . show
会出现问题,因为它会在其上添加另一层双引号(无论如何都没有意义) .因此,我们需要对 Text
、String
和 Char
类型的 Show
进行特殊处理。因此,解决方案是编写一个函数 toText :: a -> Text
并在代码生成器中使用它,如下所示:
> runQ [| toText $ 1 ::Int|]
SigE (InfixE (Just (VarE ToText.toText)) (VarE GHC.Base.$) (Just (LitE (IntegerL 1)))) (ConT GHC.Types.Int)
现在,代码生成由 toText
根据类型自行处理。这就是我在 ghc 7.10.3
中编写它的方式 - 它采用默认代码(如上所示的第一个拼接),并为某些类型重载它 - 现在,我们在 TH
codegen 位置有正确的代码在编译时:
{-# LANGUAGE FlexibleInstances #-}
module ToText
where
import Data.List
import Data.Text (unpack, pack, Text)
class ToText a where
toText :: (Show a) => a -> Text
instance {-# OVERLAPPING #-} ToText a where
toText = pack . show
instance {-# OVERLAPPING #-} ToText Char where
toText c = pack [c]
instance {-# OVERLAPPING #-} ToText String where
toText = pack
instance {-# OVERLAPPING #-} ToText Text where
toText = id
作为另一个答案的后续,这里有一些东西可以让你在没有任何重叠实例的情况下编写 ToText。它使用了我最喜欢的新技巧——将封闭类型族作为 "choice" 机制与典型类型 类 混合使用数据种类(注意:甚至不使用函数依赖性,更不用说重叠实例)来合成实际代码:
{-# LANGUAGE TypeFamilies, DataKinds, MultiParamTypeClasses, FlexibleInstances, ScopedTypeVariables, FlexibleContexts #-}
import Data.List
import Data.Text (unpack, pack, Text)
import Data.Proxy
data ToTextMethod = TTMChar | TTMString | TTMText | TTMShow
type family ToTextHow a where
ToTextHow Char = TTMChar
ToTextHow String = TTMString
ToTextHow Text = TTMText
ToTextHow a = TTMShow
class ToTextC a b where
toTextC :: a -> b -> Text
instance Show a => ToTextC a (Proxy TTMShow) where
toTextC a _ = pack (show a)
instance ToTextC Char (Proxy TTMChar) where
toTextC c _ = pack [c]
instance ToTextC String (Proxy TTMString) where
toTextC s _ = pack s
instance ToTextC Text (Proxy TTMText) where
toTextC t _ = t
toText :: forall a. (Show a, ToTextC a (Proxy (ToTextHow a))) => a -> Text
toText x = toTextC x (Proxy :: Proxy (ToTextHow a))
名称可能需要一些工作,将参数翻转为 toTextC
可能会很好,但这即使在 ghc 7.8.3 中也能正常工作。
我在 Template Haskell
中有一个函数可以提取记录构造函数总和的类型信息,如下所示:
listFields :: Name -> Q ([[(String,Name,Type)]])
listFields name = do
TyConI (DataD _ _ _ cons _) <- reify name
let showClause (RecC conName fields) = (map (\(x,_,t) -> (nameBase $ x,x,t)) fields)
return $ map showClause cons
给定其中的字段类型,您如何比较该类型与 GHC.Base.String
或 Data.Text.Internal.Text
等特定类型的相等性?我在 TH
文档中看到 TypeQ
。它构建类型表达式。但是,我找不到任何关于如何构建特定类型(如 String
或 Text
或 Int
以便我可以将其用于相等比较的文档?将感谢有关如何执行此操作的指示,尤其是如何获取特定类型的 AST。
这个问题的原因是给定的记录构造函数,我们想将每个字段转换为Text
。但是,对于 String
和 Text
类型,show
和 pack
应该有不同的调用方式。因此,如果类型是Text
(不转换)或String
(只调用pack
,不调用show
)或其他类型(调用pack . show
假设存在 Show
个实例)。
根据评论中 jozefg
的建议,我通过使用具有类型签名 a -> Text
的重载函数解决了这个问题。再开几天看看有没有人有更好的建议。
这是我原来的TH
拼接(ghci
输出):
> runQ [| pack . show $ 1 ::Int|]
SigE (InfixE (Just (InfixE (Just (VarE Data.Text.pack)) (VarE GHC.Base..)
(Just (VarE GHC.Show.show)))) (VarE GHC.Base.$) (Just (LitE (IntegerL 1))))
(ConT GHC.Types.Int)
Int
转换为 Text
。但是,String
或 Text
上的 运行 pack . show
会出现问题,因为它会在其上添加另一层双引号(无论如何都没有意义) .因此,我们需要对 Text
、String
和 Char
类型的 Show
进行特殊处理。因此,解决方案是编写一个函数 toText :: a -> Text
并在代码生成器中使用它,如下所示:
> runQ [| toText $ 1 ::Int|]
SigE (InfixE (Just (VarE ToText.toText)) (VarE GHC.Base.$) (Just (LitE (IntegerL 1)))) (ConT GHC.Types.Int)
现在,代码生成由 toText
根据类型自行处理。这就是我在 ghc 7.10.3
中编写它的方式 - 它采用默认代码(如上所示的第一个拼接),并为某些类型重载它 - 现在,我们在 TH
codegen 位置有正确的代码在编译时:
{-# LANGUAGE FlexibleInstances #-}
module ToText
where
import Data.List
import Data.Text (unpack, pack, Text)
class ToText a where
toText :: (Show a) => a -> Text
instance {-# OVERLAPPING #-} ToText a where
toText = pack . show
instance {-# OVERLAPPING #-} ToText Char where
toText c = pack [c]
instance {-# OVERLAPPING #-} ToText String where
toText = pack
instance {-# OVERLAPPING #-} ToText Text where
toText = id
作为另一个答案的后续,这里有一些东西可以让你在没有任何重叠实例的情况下编写 ToText。它使用了我最喜欢的新技巧——将封闭类型族作为 "choice" 机制与典型类型 类 混合使用数据种类(注意:甚至不使用函数依赖性,更不用说重叠实例)来合成实际代码:
{-# LANGUAGE TypeFamilies, DataKinds, MultiParamTypeClasses, FlexibleInstances, ScopedTypeVariables, FlexibleContexts #-}
import Data.List
import Data.Text (unpack, pack, Text)
import Data.Proxy
data ToTextMethod = TTMChar | TTMString | TTMText | TTMShow
type family ToTextHow a where
ToTextHow Char = TTMChar
ToTextHow String = TTMString
ToTextHow Text = TTMText
ToTextHow a = TTMShow
class ToTextC a b where
toTextC :: a -> b -> Text
instance Show a => ToTextC a (Proxy TTMShow) where
toTextC a _ = pack (show a)
instance ToTextC Char (Proxy TTMChar) where
toTextC c _ = pack [c]
instance ToTextC String (Proxy TTMString) where
toTextC s _ = pack s
instance ToTextC Text (Proxy TTMText) where
toTextC t _ = t
toText :: forall a. (Show a, ToTextC a (Proxy (ToTextHow a))) => a -> Text
toText x = toTextC x (Proxy :: Proxy (ToTextHow a))
名称可能需要一些工作,将参数翻转为 toTextC
可能会很好,但这即使在 ghc 7.8.3 中也能正常工作。