管理约束的爆炸式增长 (Haskell)

Managing explosion of constraints (Haskell)

我正在编写一个函数,我希望它是 Vectors 的通用函数。换句话说,我有类似的东西:

import qualified Data.Vector.Generic as G
foo :: (G.Vector v a, G.Vector v b, G.Vector v c, G.Vector v d) 
    => v a -> v b -> v c -> v d 

这样用户就可以选择使用 Unboxed 还是 Storable 矢量等

但是如果我需要将中间值放在 v 中,我会得到 Vector 约束的组合爆炸,例如:

foo :: (G.Vector v a, G.Vector v b, G.Vector v c, G.Vector v d,
        G.Vector v (a, b), G.Vector v (a, c), G.Vector v (c, b))
    => v a -> v b -> v c -> v d 

如何管理这种冗长的内容?有没有办法让

1) GHC 隐式生成约束

2) 以某种方式将约束重构为通用class

类型族可能很方便:

{-# LANGUAGE TypeFamilies, KindSignatures, DataKinds, TypeOperators, ConstraintKinds #-}

import qualified Data.Vector.Generic as G
import GHC.Prim

type family Vectors v (a :: [*]) :: Constraint where
  Vectors v '[]       = ()
  Vectors v (a ': as) = (G.Vector v a, Vectors v as)

这将创建一个类型级函数,该函数采用类型列表并为具有这些类型的泛型 Vector 设置适当的约束。你可以这样使用它:

foo :: (Vectors v [a, b, c, d, (a, b), (a, c), (c, b)]) => v a -> v b -> v c -> v d

这不是很理想,但确实在一定程度上减少了代码。

处理元组约束的急剧增加

您可以使用更多类型级别的技巧更好地处理这种由对类型引起的约束爆炸:

type family Combinations v (a :: [*]) :: Constraint where
  Combinations v '[]       = ()
  Combinations v (a ': as) = (G.Vector v a, CombComponent v a as, Combinations v as)

type family CombComponent v (a :: *) (bs :: [*]) :: Constraint where
  CombComponent v a '[]       = ()
  CombComponent v a (b ': bs) = (G.Vector v (a, b), G.Vector v (b, a), CombComponent v a bs)

有点复杂,但现在我们可以这样写第二个 foo 签名:

foo :: (Combinations v [a, b, c, d]) => v a -> v b -> v c -> v d

这也可以进一步推广以允许像这样的签名:

foo :: (Combinations (G.Vector v) [a, b, c, d]) => v a -> v b -> v c -> v d