我发现了如何处理由单个类约束的异构类型列表:
data Ex c = forall a. (c a) => Ex a
forEx :: [Ex c] -> (forall a. c a => a -> b) -> [b]
forEx [] _ = []
forEx (Ex a:r) f = f a : forEx r f
> forEx @Show [Ex 3, Ex (), Ex True] show
["3","()","True"]
看起来很棒,但在现实生活中,依赖一个以上约束的函数会更复杂,而不是显示函数。进一步类推是行不通的:
尝试1:
forEx @(Show, Eq) [Ex 3, Ex (), Ex True] show
<interactive>:85:8: error:
• Expected kind ‘* -> Constraint’, but ‘(Show, Eq)’ has kind ‘*’
尝试2:
type ShowEq c = (Show c, Eq c)
> forEx @ShowEq [Ex 3, Ex (), Ex True] show
<interactive>:87:1: error:
• The type synonym ‘ShowEq’ should have 1 argument, but has been given none
尝试3起作用,但定义一次使用的伪类和实例太笨拙了:
class (Show a, Eq a) => ShowEq1 a
instance (Show a, Eq a) => ShowEq1 a
forEx @ShowEq1 [Ex (3::Int), Ex (), Ex True] show
["3","()","True"]
有了足够的语言功能,就可以创建一个结合约束的约束:
{-# LANGUAGE RankNTypes, ConstraintKinds, GADTs, TypeApplications, MultiParamTypeClasses, FlexibleInstances, UndecidableSuperClasses #-}
data Ex c = forall a. (c a) => Ex a
forEx :: [Ex c] -> (forall a. c a => a -> b) -> [b]
forEx [] _ = []
forEx (Ex a:r) f = f a : forEx r f
class (c a, d a) => (Combined c d) a
instance (c a, d a) => (Combined c d) a
shown :: [String]
shown = forEx @(Combined Show Eq) [Ex 3, Ex (), Ex True] show
我会这样做:
{-# LANGUAGE DataKinds, GADTs, RankNTypes, StandaloneKindSignatures, TypeFamilies, TypeOperators #-}
import Data.Kind
type All :: [Type -> Constraint] -> Type -> Constraint
type family All cs t
where All '[] _ = ()
All (c ': cs) t = (c t, All cs t)
data Ex cs
where Ex :: All cs t => t -> Ex cs
forEx :: [Ex cs] -> (forall a. All cs a => a -> b) -> [b]
forEx [] _ = []
forEx (Ex x : xs) f = f x : forEx xs f
现在Ex
不是在单个类上参数化的,而是在类1的列表上参数化。我们有All
类型族,用于获取类的列表,并将它们全部应用于同一类型,将它们组合为一个Constraint
。
这意味着(与将其他两个类组合在一起的类方法不同(它现在支持您需要的任何数量的约束。
你可以这样称呼它2:
λ forEx @[Show, Eq] [Ex 3, Ex (), Ex True] show
["3","()","True"]
it :: [String]
1从技术上讲,这不是一个类的列表,而是一个需要多个类型参数才能生成Constraint
的列表。不过,单参数类将是最常用的方法。
2当然,Eq
不是一个非常有用的约束,因为如果没有另一个相同未知类型的值,你就无法实际使用它,而且你已经扔掉了关于任何给定类型是否是同一类型的所有信息。