如何在 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 并不是一个非常有用的约束条件,因为如果没有 另一个 相同未知类型的值,并且您已经丢弃了关于任何给定类型 是否 相同类型的所有信息。