混淆类型推断和函数依赖

Confusion with type inference and functional dependencies

我有以下多参数类型类,它对作为向量空间(向量)元素的类型具有函数依赖性

module Vec where

class Vec v k | v -> k where  -- v is an element of a vector space over k
    vZero :: v                -- The zero vector in v
    vAdd  :: v -> v -> v      -- Adds two vectors
    vSub  :: v -> v -> v      -- Subtracts two vectors
    vMul  :: v -> k -> v      -- Multiplies a vector by a number from k

infixl 6 |+|   -- Shortcut operator for accessing vAdd
(|+|) :: Vec v k => v -> v -> v
(|+|) = vAdd

现在我将上面的代码加载到 ghci 解释器中并要求它显示运算符的类型 |+|:

*Vec> :t (|+|)
(|+|) :: Vec v k => v -> v -> v

到目前为止,一切似乎都很正常。但是现在我想指定所有数字都是它们自身上的特殊向量空间的元素:

instance Num k => Vec k k where
    vZero = 0
    vAdd = (+)
    vSub = (-)
    vMul = (*)

现在发生了一件奇怪的事情:ghci 不再显示 |+| 的正确类型(尽管我在上面的代码中明确指定了它):

*Vec> :t (|+|)
(|+|) :: Num v => v -> v -> v

我怀疑这种奇怪的行为与我正在使用的 FunctionalDependencies 语言扩展有关,但我不明白为什么 ghc 会这样。我可以看到自己添加了一个不同的实例 Vec v k,其中 v 而不是 Num 的实例,因此这样的实例不会与现有实例重叠,从而保留功能依赖性。

您定义了非常通用的实例:Vec v ...。 没有重叠实例就不可能有其他实例。

例如添加

data V2 k = V2 k k
instance Num k => Vec (V2 k) k where

结果进入

Functional dependencies conflict between instance declarations:
  instance Num k => Vec k k -- Defined at v.hs:15:10
  instance Num k => Vec (V2 k) k -- Defined at v.hs:23:10

实际上重叠的实例在这里也无济于事(这可能是 GHC 遗漏的功能?)。

如果您尝试使用 TypeFamilies 对其进行编码,您会得到类似的错误:

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
module Vec where

class Vec v where  -- v is an element of a vector space over k
    type Elem v
    vZero :: v                 -- The zero vector in v
    vAdd  :: v -> v -> v       -- Adds two vectors
    vSub  :: v -> v -> v       -- Subtracts two vectors
    vMul  :: v -> Elem v -> v  -- Multiplies a vector by a number from k

infixl 6 |+|   -- Shortcut operator for accessing vAdd
(|+|) :: Vec v k => v -> v -> v
(|+|) = vAdd

data V2 k = V2 k k

instance Num k => Vec (V2 k) where
    type Elem (V2 k) = k
    -- implementation omitted

instance Num k => Vec k where
    type Elem k = k
    vZero = 0
    vAdd = (+)
    vSub = (-)
    vMul = (*)

错误:

Conflicting family instance declarations:
  Elem (V2 k) -- Defined at v.hs:20:10
  Elem k -- Defined at v.hs:23:10

解决方案是定义辅助函数并编写实例定义"by hand":

{-# LANGUAGE FlexibleInstances, FunctionalDependencies #-}
module Vec where

class Vec v k | v -> k where  -- v is an element of a vector space over k
    vZero :: v                -- The zero vector in v
    vAdd  :: v -> v -> v      -- Adds two vectors
    vSub  :: v -> v -> v      -- Subtracts two vectors
    vMul  :: v -> k -> v      -- Multiplies a vector by a number from k

infixl 6 |+|   -- Shortcut operator for accessing vAdd
(|+|) :: Vec v k => v -> v -> v
(|+|) = vAdd

-- The definitions are so short, that it's not worth even givin them names
numVZero :: Num k => k
numVZero = 0

instance Vec Int Int where
    vZero = 0
    vAdd = (+)
    vSub = (-)
    vMul = (*)

data V2 k = V2 k k
instance Num k => Vec (V2 k) k where
    vZero = V2 0 0
    vAdd (V2 a b) (V2 c d) = V2 (a + c) (b + d)
    vSub (V2 a b) (V2 c d) = V2 (a - c) (b - d)
    vMul (V2 a b) k        = V2 (a * k) (b * k)

然后:

λ *Vec > :t (|+|)
(|+|) :: Vec v k => v -> v -> v

或者由于 Num v => Vec v v 可能会很常见,您可以使用 DefaultSignatures 减少实例声明的样板:

{-# LANGUAGE GADTs, FlexibleInstances, FunctionalDependencies, DefaultSignatures #-}
module Vec where

class Vec v k | v -> k where  -- v is an element of a vector space over k
    vZero :: v                -- The zero vector in v
    default vZero :: (Num v, v ~ k) => v
    vZero = 0

    vAdd  :: v -> v -> v      -- Adds two vectors
    default vAdd :: (Num v, v ~ k) => v -> v -> v
    vAdd = (+)

    vSub  :: v -> v -> v      -- Subtracts two vectors
    default vSub :: (Num v, v ~ k) => v -> v -> v
    vSub = (-)

    vMul  :: v -> k -> v      -- Multiplies a vector by a number from k
    default vMul :: (Num v, v ~ k) => v -> k -> v
    vMul = (*)

infixl 6 |+|   -- Shortcut operator for accessing vAdd
(|+|) :: Vec v k => v -> v -> v
(|+|) = vAdd

instance Vec Int Int
instance Vec Integer Integer
instance Vec Float Float
instance Vec Double Double

data V2 k = V2 k k
instance Num k => Vec (V2 k) k where
    vZero = V2 0 0
    vAdd (V2 a b) (V2 c d) = V2 (a + c) (b + d)
    vSub (V2 a b) (V2 c d) = V2 (a - c) (b - d)
    vMul (V2 a b) k        = V2 (a * k) (b * k)