如何在 Haskell 中映射具有多个约束的存在?
How to map existentials with multiple constraints in Haskell?
我想出了如何处理由单个 class 约束的异构类型列表:
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 个约束的 show 函数会更复杂。
类推不行:
尝试 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 和一次性使用的实例是笨拙的:
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
不是在单个 class 上参数化,而是在 classes1[= 的 list 上参数化39=]。我们有 All
类型族,用于获取 classes 的列表并将它们全部应用于同一类型,将它们组合成单个 Constraint
.
这意味着(与 class-to-combine-two-other-classes 方法不同)它现在支持您需要的任意数量的约束。
你可以这样称呼它2:
λ forEx @[Show, Eq] [Ex 3, Ex (), Ex True] show
["3","()","True"]
it :: [String]
1 从技术上讲,这不是 class 的列表,而是需要一个类型参数才能生成 Constraint
的列表。不过,单参数 classes 将是您最常使用它的东西。
2 当然,Eq
并不是一个非常有用的约束条件,因为如果没有 另一个 相同未知类型的值,并且您已经丢弃了关于任何给定类型 是否 相同类型的所有信息。
我想出了如何处理由单个 class 约束的异构类型列表:
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 个约束的 show 函数会更复杂。 类推不行:
尝试 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 和一次性使用的实例是笨拙的:
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
不是在单个 class 上参数化,而是在 classes1[= 的 list 上参数化39=]。我们有 All
类型族,用于获取 classes 的列表并将它们全部应用于同一类型,将它们组合成单个 Constraint
.
这意味着(与 class-to-combine-two-other-classes 方法不同)它现在支持您需要的任意数量的约束。
你可以这样称呼它2:
λ forEx @[Show, Eq] [Ex 3, Ex (), Ex True] show
["3","()","True"]
it :: [String]
1 从技术上讲,这不是 class 的列表,而是需要一个类型参数才能生成 Constraint
的列表。不过,单参数 classes 将是您最常使用它的东西。
2 当然,Eq
并不是一个非常有用的约束条件,因为如果没有 另一个 相同未知类型的值,并且您已经丢弃了关于任何给定类型 是否 相同类型的所有信息。