我弄清楚了如何处理受单个类约束的异构类型列表:
\ndata Ex c = forall a. (c a) => Ex a\n\nforEx :: [Ex c] -> (forall a. c a => a -> b) -> [b] \nforEx [] _ = [] \nforEx (Ex a:r) f = f a : forEx r f\n\n> forEx @Show [Ex 3, Ex (), Ex True] show\n["3","()","True"]\nRun Code Online (Sandbox Code Playgroud)\n看起来不错,但在现实生活中,与 show 函数相比,它会更复杂,依赖于 1 个以上的约束。\n进一步类推是行不通的:
\n尝试1:
\nforEx @(Show, Eq) [Ex 3, Ex (), Ex True] show\n\n<interactive>:85:8: error:\n \xe2\x80\xa2 Expected kind \xe2\x80\x98* -> Constraint\xe2\x80\x99, but \xe2\x80\x98(Show, Eq)\xe2\x80\x99 has kind \xe2\x80\x98*\xe2\x80\x99\nRun Code Online (Sandbox Code Playgroud)\n尝试2:
\ntype ShowEq c = (Show c, Eq c)\n> forEx @ShowEq [Ex 3, Ex (), Ex True] show\n\n<interactive>:87:1: error:\n \xe2\x80\xa2 The type synonym \xe2\x80\x98ShowEq\xe2\x80\x99 should have 1 argument, but has been given none\nRun Code Online (Sandbox Code Playgroud)\n尝试 3 可行,但定义一次性使用的虚拟类和实例很笨拙:
\nclass (Show a, Eq a) => ShowEq1 a \ninstance (Show a, Eq a) => ShowEq1 a\nforEx @ShowEq1 [Ex (3::Int), Ex (), Ex True] show\n\n["3","()","True"]\nRun Code Online (Sandbox Code Playgroud)\n
我会这样做:
\n{-# LANGUAGE DataKinds, GADTs, RankNTypes, StandaloneKindSignatures, TypeFamilies, TypeOperators #-}\n\nimport Data.Kind\n\ntype All :: [Type -> Constraint] -> Type -> Constraint\ntype family All cs t\n where All '[] _ = ()\n All (c ': cs) t = (c t, All cs t)\n\ndata Ex cs\n where Ex :: All cs t => t -> Ex cs\n\nforEx :: [Ex cs] -> (forall a. All cs a => a -> b) -> [b]\nforEx [] _ = []\nforEx (Ex x : xs) f = f x : forEx xs f\nRun Code Online (Sandbox Code Playgroud)\n现在Ex参数化的不是单个类,而是类列表1。我们有All类型族,用于获取类列表并将它们全部应用于同一类型,将它们组合成一个Constraint.
这意味着(与类组合两个其他类的方法不同)它现在支持您需要的任意数量的约束。
\n你可以这样称呼它2:
\n\xce\xbb forEx @[Show, Eq] [Ex 3, Ex (), Ex True] show\n["3","()","True"]\nit :: [String]\nRun Code Online (Sandbox Code Playgroud)\n1从技术上讲,这不是一个类列表,而是一个需要多一个类型参数才能生成Constraint. 不过,单参数类将是您最常用的东西。
2当然,这并不是一个非常有用的约束,因为如果没有相同未知类型的另一个Eq值,您实际上无法使用它,并且您已经丢弃了有关任何给定类型是否相同类型的所有信息。