如何在Haskell中映射具有多个约束的存在论



我发现了如何处理由单个类约束的异构类型列表:

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不是一个非常有用的约束,因为如果没有另一个相同未知类型的值,你就无法实际使用它,而且你已经扔掉了关于任何给定类型是否是同一类型的所有信息。

最新更新