使用 Data 和 Typeable 获取构造函数的参数类型
Getting the argument types of a constructor using Data and Typeable
我正在玩弄 Haskell 的 Data and Typeable,我一直在尝试获取函数的参数,而上下文中没有可用的类型变量。
让我澄清一下我的意思。只要我有像下面这样量化的类型变量 a
,我就可以使用 fromConstr
并根据需要获得 DataType
或 TypeRep
的列表:
constrArgs :: forall a. Data a => Constr -> [DataType]
constrArgs c = gmapQ f (fromConstr c :: a)
where f :: forall d. Data d => d -> DataType
f _ = dataTypeOf @d undefined
(我意识到 undefined
和 fromConstr
是不完全的,但懒惰在这里拯救了我们。)
但是,如果我试图避免量化 a
,我将无法再对 fromConstr
的结果进行类型归属。我想知道是否有一种方法可以编写具有以下类型签名的函数:
constrArgs' :: Constr -> [DataType]
我的最终目标是编写一个函数,它给出 DataType
列表的列表,每个构造函数的子列表,每个子列表包含该构造函数的参数类型。使用第一个版本,写一个带有类型签名的函数并不难:(省略定义)
allConstrArgs :: forall a. Data a => [[DataType]]
这个问题是我无法将 allConstrArgs
应用于其自身的结果,因为无法从 DataType
到类型级值。
那么,为了修改它,我们可以编写一个具有以下类型的函数吗?
allConstrsArgs' :: DataType -> [[DataType]]
我查看了基础库,但没有看到如何实现。
您无法从 Constr
中获取参数类型列表,因为其中没有足够的数据:它只是一堆字符串,仅此而已。
但是,有一种方法可以实现您更大的目标:您只需要随身携带 Data
字典,还有什么比存在类型更好的方法呢!
data D = forall a. Data a => D a
allConstrArgs :: D -> [[D]]
allConstrArgs d = constrArgs d <$> allConstrs d
constrArgs :: D -> Constr -> [D]
constrArgs (D a) c = gmapQ D $ mkConstr a c
where
mkConstr :: forall a. Data a => a -> Constr -> a
mkConstr _ = fromConstr
allConstrs :: D -> [Constr]
allConstrs (D a) = case dataTypeRep $ dataTypeOf a of
AlgRep constrs -> constrs
_ -> []
mkD :: forall a. Data a => D
mkD = D (undefined :: a)
此处类型 D
仅用于包装 Data
字典 - 实际值 a
将始终为 undefined
,并且从未实际计算过,所以这很好.因此,值 D
用作类型的值级别表示,因此在解构时,您会在范围内获得该类型的 Data
实例。
函数 constrArgs
采用类型表示 D
和构造函数 Constr
,以及 returns 该构造函数参数的列表,每个参数表示为 D
同样 - 所以现在您可以将它的输出反馈到它的输入中!它通过使用 gmapQ
来实现,其第一个参数类型完全符合 D
构造函数。
mkD
只是一个效用函数,旨在隐藏 undefined
的不愉快之处,并与 TypeApplications
一起使用,例如mkD @Int
.
这是用法:
data X = X0 Int | X1 String deriving (Typeable, Data)
data Y = Y0 String | Y1 Bool | Y2 Char deriving (Typeable, Data)
data Z = ZX X | ZY Y deriving (Typeable, Data)
typName :: D -> String
typName (D a) = dataTypeName $ dataTypeOf a
main = do
-- Will print [["Prelude.Int"],["Prelude.[]"]]
print $ map typName <$> allConstrArgs (mkD @X)
-- Will print [["Prelude.[]"],["Bool"],["Prelude.Char"]]
print $ map typName <$> allConstrArgs (mkD @Y)
-- Will print [["X"],["Y"]]
print $ map typName <$> allConstrArgs (mkD @Z)
请注意,您需要以下扩展程序才能正常工作:ScopedTypeVariables, DeriveDataTypeable, GADTs, AllowAmbiguousTypes, TypeApplications
我正在玩弄 Haskell 的 Data and Typeable,我一直在尝试获取函数的参数,而上下文中没有可用的类型变量。
让我澄清一下我的意思。只要我有像下面这样量化的类型变量 a
,我就可以使用 fromConstr
并根据需要获得 DataType
或 TypeRep
的列表:
constrArgs :: forall a. Data a => Constr -> [DataType]
constrArgs c = gmapQ f (fromConstr c :: a)
where f :: forall d. Data d => d -> DataType
f _ = dataTypeOf @d undefined
(我意识到 undefined
和 fromConstr
是不完全的,但懒惰在这里拯救了我们。)
但是,如果我试图避免量化 a
,我将无法再对 fromConstr
的结果进行类型归属。我想知道是否有一种方法可以编写具有以下类型签名的函数:
constrArgs' :: Constr -> [DataType]
我的最终目标是编写一个函数,它给出 DataType
列表的列表,每个构造函数的子列表,每个子列表包含该构造函数的参数类型。使用第一个版本,写一个带有类型签名的函数并不难:(省略定义)
allConstrArgs :: forall a. Data a => [[DataType]]
这个问题是我无法将 allConstrArgs
应用于其自身的结果,因为无法从 DataType
到类型级值。
那么,为了修改它,我们可以编写一个具有以下类型的函数吗?
allConstrsArgs' :: DataType -> [[DataType]]
我查看了基础库,但没有看到如何实现。
您无法从 Constr
中获取参数类型列表,因为其中没有足够的数据:它只是一堆字符串,仅此而已。
但是,有一种方法可以实现您更大的目标:您只需要随身携带 Data
字典,还有什么比存在类型更好的方法呢!
data D = forall a. Data a => D a
allConstrArgs :: D -> [[D]]
allConstrArgs d = constrArgs d <$> allConstrs d
constrArgs :: D -> Constr -> [D]
constrArgs (D a) c = gmapQ D $ mkConstr a c
where
mkConstr :: forall a. Data a => a -> Constr -> a
mkConstr _ = fromConstr
allConstrs :: D -> [Constr]
allConstrs (D a) = case dataTypeRep $ dataTypeOf a of
AlgRep constrs -> constrs
_ -> []
mkD :: forall a. Data a => D
mkD = D (undefined :: a)
此处类型 D
仅用于包装 Data
字典 - 实际值 a
将始终为 undefined
,并且从未实际计算过,所以这很好.因此,值 D
用作类型的值级别表示,因此在解构时,您会在范围内获得该类型的 Data
实例。
函数 constrArgs
采用类型表示 D
和构造函数 Constr
,以及 returns 该构造函数参数的列表,每个参数表示为 D
同样 - 所以现在您可以将它的输出反馈到它的输入中!它通过使用 gmapQ
来实现,其第一个参数类型完全符合 D
构造函数。
mkD
只是一个效用函数,旨在隐藏 undefined
的不愉快之处,并与 TypeApplications
一起使用,例如mkD @Int
.
这是用法:
data X = X0 Int | X1 String deriving (Typeable, Data)
data Y = Y0 String | Y1 Bool | Y2 Char deriving (Typeable, Data)
data Z = ZX X | ZY Y deriving (Typeable, Data)
typName :: D -> String
typName (D a) = dataTypeName $ dataTypeOf a
main = do
-- Will print [["Prelude.Int"],["Prelude.[]"]]
print $ map typName <$> allConstrArgs (mkD @X)
-- Will print [["Prelude.[]"],["Bool"],["Prelude.Char"]]
print $ map typName <$> allConstrArgs (mkD @Y)
-- Will print [["X"],["Y"]]
print $ map typName <$> allConstrArgs (mkD @Z)
请注意,您需要以下扩展程序才能正常工作:ScopedTypeVariables, DeriveDataTypeable, GADTs, AllowAmbiguousTypes, TypeApplications