为参数类型实现 Functor

Implementing Functor for a parametric type

有这种类型:

{-# LANGUAGE GADTs #-}

data Rgb a = (Num a, Show a) => Rgb a a a

我完全能够实现 Show 类型类:

instance Show (Rgb a) where
  show (Rgb r g b) = "Rgb (" ++ show r ++ "," ++ show g ++ "," ++ show b ++ ")"

但是如果我尝试对 Functor 做同样的事情:

instance Functor (Rgb a) where
  fmap f (Rgb r g b) = Rgb (f r) (f g) (f b)

我在 GHCi REPL 上得到以下输出:

<interactive>;:1093:19:
  The first argument of ‘Functor’ should have kind ‘* > *’,
    but ‘Rgb a’ has kind ‘*’
  In the instance declaration for ‘Functor (Rgb a)’

我当然会对解决方案和解释感到满意,但也会link深化与这个问题相关的理论。

为了克服这个问题,我(暂时)编写了这个函数:

mapRgb :: (Num a, Num b, Show a, Show b) => (a -> b) -> Rgb a -> Rgb b
mapRgb f (Rgb r g b) = Rgb (f r) (f g) (f b)

但我真的更喜欢为 Rgb 类型实现 fmap

您的 Functor 实例不应该有类型参数:

instance Functor Rgb where
  fmap f (Rgb r g b) = Rgb (f r) (f g) (f b)

如果要派生实例,包括 Functor,请使用 DeriveFunctor pragma:

{-# LANGUAGE DeriveFunctor #-}

data Rgb a = Rgb a a a                   -- NOTE: DO NOT CONSTRAIN DATA!
    deriving (Show, Eq, Ord, Functor)

此外,对数据类型声明的类型约束几乎总是无用的。约束需要这些约束的函数


您发现的问题是由于类型的类型:种类。我们用*写种类,GHCi中的:kind命令可以帮助:

λ> :kind Int
Int :: *
λ> :kind Char
Char :: *
λ> :kind Maybe Int
Maybe Int :: *

所有 Functor 都采用类型参数,因此它们看起来都像这样:

λ> :kind Maybe
Maybe :: * -> *
λ> :kind IO
IO :: * -> *

RGB 属于 * -> *,但是当你写 RGB a 时,你将 a :: * 应用到它,它变成 RGB a :: *,这不是'对编译器没有意义。

现在您应该明白了:

The first argument of ‘Functor’ should have kind ‘* > *’,
  but ‘Rgb a’ has kind ‘*’

之前您尝试实现仿函数实例时失败的原因是因为您的数据类型存在以下限制:

-- Do not do this. This is poor Haskell.
data Rgb a = (Num a, Show a) => Rgb a a a

你应该写:

data Rgb a = Rgb a a a

然后在每个实例上添加约束:

instance (Show a) => Show (RGB a) where
    ...

instance (Num a) => Num (RGB a) where
    ...

然后你的仿函数实例就没问题了。