在类型 class 中使用实例类型参数

Use instance type parameter in type class

作为练习,我正在尝试创建一个 Vector 类型类作为练习:

class Vector v where
  vplus :: v -> v -> v
  vnegate :: v -> v

type V3 a = (a,a,a)

instance (Num a) => Vector (V3 a) where
  (a,b,c) `vplus` (d,e,f) = (a+d, b+e, c+f)
  vnegate (a,b,c) = ((-a), (-b), (-c))

我想在类型类上添加一个 dot 函数。在上面的 V3 示例中,我将按如下方式实现它:

dot :: (Num a) => V3 a -> V3 a -> a
(a,b,c) `dot` (d,e,f) = a*d + b*e + c*f

但是,我似乎无法从 Vector 中访问类型参数 a,所以我无法让 dot 操作 Vector我想要的方式。我将如何访问 a 类型参数?

您想为此使用 TypeFamilies 创建关联类型:

{-# LANGUAGE TypeFamilies, TypeSynonymInstances, FlexibleInstances #-}

class Vector v where
    -- Declares a family of types called Item, parametrized on the
    -- instance v of Vector, and the kind of Item v must be *,
    -- meaning that it must be a type, not a type constructor
    -- (e.g. Maybe Int :: * vs Maybe :: * -> *)
    type family Item v :: *
    dot :: v -> v -> Item v
    ...

instance (Num a) => Vector (V3 a) where
    type Item (V3 a) = a
    dot (a, b, c) (d, e, f) = a*d + b*e + c*f
    ...

那你可以做

> dot (1, 2, 3) ((4, 5, 6) :: V3 Int)
32

尽管我建议不要使用类型同义词实例,但最好使用数据类型:

data V3 a = V3 a a a deriving (Eq, Show)

instance Functor V3 where
    fmap f (V3 a b c) = V3 (f a) (f b) (f c)

instance (Num a) => Vector (V3 a) where
    type Item (V3 a) = a
    (V3 a b c) `vplus` (V3 d e f) = V3 (a + d) (b + e) (c + f)
    vnegate v = fmap negate v
    dot (V3 a b c) (V3 d e f) = a*d + b*e + c*f

这对类型检查器有很大帮助,特别是它意味着您不需要上面的显式类型签名。这也意味着您的推断类型不会是 (a, a, a),而是 V3 a(就像当您看到 [Char] 而不是 String 时),这更容易理解。这不是关键,但很有帮助。

如果您想知道,这就是 GHC.Exts.IsList(与新的 OverloadedLists 扩展一起使用)是如何做到的:

class IsList l where
    type family GHC.Exts.Item l :: *
    fromList :: [GHC.Exts.Item l] -> l
    fromListN :: Int -> [GHC.Exts.Item l] -> l
    toList :: l -> [GHC.Exts.Item l]

另一种不需要扩展的解决方案是使用更高种类的 class。因此:

class Vector v where
    vplus :: Num a => v a -> v a -> v a
    vnegate :: Num a => v a -> v a

然后加个点积的方法就很简单了:

    dot :: Num a => v a -> v a -> a

实例方法实现不必更改,但实例声明本身必须更改:

instance Vector V3 where
    -- method implementations are the same as before