使用Data和Typeable获取构造函数的参数类型

Joo*_*kut 3 reflection haskell types generic-programming

我在玩Haskell的DataTypeable,并且一直在尝试获取函数的参数而上下文中没有类型变量。

让我澄清一下我的意思。只要我按a如下方式量化类型变量,就可以使用fromConstr并获得DataTypeTypeRep希望的列表:

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
Run Code Online (Sandbox Code Playgroud)

(我认识到undefinedfromConstr并非总和,但懒惰在这里为我们节省了费用。)

但是,如果我尝试避免量化a,则无法再对的结果进行类型归因fromConstr。我想知道是否有一种方法可以编写具有以下类型签名的函数:

constrArgs' :: Constr -> [DataType]
Run Code Online (Sandbox Code Playgroud)

我的最终目标是编写一个函数,该函数给出DataTypes列表的列表,每个构造函数的子列表,每个子列表均包含该构造函数的参数类型。使用第一个版本,编写带有类型签名的函数并不困难:(取消定义)

allConstrArgs :: forall a. Data a => [[DataType]]
Run Code Online (Sandbox Code Playgroud)

问题是我无法应用于allConstrArgs自身的结果,因为没有办法将其DataType转换为类型级别的值。

因此,为了对此进行修改,我们可以编写具有以下类型的函数吗?

allConstrsArgs' :: DataType -> [[DataType]]
Run Code Online (Sandbox Code Playgroud)

我在基本库中环顾四周,但看不到如何实现。

Fyo*_*kin 5

您无法从中获取参数类型的列表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)
Run Code Online (Sandbox Code Playgroud)

这里的类型D仅用于包装Data字典-实际值a始终为undefined,从不实际求值,所以很好。D因此,该值用作类型的值级别表示,以便在解构时获得Data作用域中该类型的实例。

该函数constrArgs接受一个类型表示形式D和一个构造函数Constr,并返回该构造函数的参数列表,每个参数也都表示了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)
Run Code Online (Sandbox Code Playgroud)

请注意,您将需要以下扩展才能正常工作: ScopedTypeVariables, DeriveDataTypeable, GADTs, AllowAmbiguousTypes, TypeApplications