这是函子类:
class Functor f where
fmap :: (a -> b) -> f a -> f b
请注意,“f”本身是一个类型构造函数,因为它应用于 fmap 行中的类型变量。这里有一些例子可以说明这一点:
类型构造函数:
IO
Maybe
Either String
类型:
IO Char
Maybe a
Either String String
“Maybe a”是具有一个类型构造函数(“Maybe”)和一个类型变量(“a”)的类型。它还不是具体的东西,但它可用于多态函数的类型签名。
“Either”是一个接受两个类型参数的类型构造函数,所以即使你应用了一个(例如Either String
,它仍然是一个类型构造函数,因为它可以接受另一个类型参数。
这一点是:当你定义一个Functor
实例时,类型构造函数f
不能改变。 这是因为它由相同的变量 表示f
,作为 的参数和结果fmap
。唯一允许更改的类型是应用于f
构造函数的类型。
在你写的时候instance Functor (Either c)
,是在声明中到处Either c
填的。这为这个实例提供了以下类型的 fmap:f
fmap
fmap :: (a -> b) -> (Either c) a -> (Either c) b
有了 的定义Either
,获得此类型的唯一有用方法是将 Right
值应用于函数。请记住,“Either”有两个可能具有不同类型的值。这里的Left
值的类型是'c',所以你不能将它应用到函数(它需要一个'a')[1],结果也不正确,因为你会留下Either b a
,这不会'不匹配类定义。
在将“f”替换为“Either c”以获得上述带有“Either c”实例的fmap的类型签名之后,接下来是编写实现。有两种情况需要考虑,左派和右派。类型签名告诉我们左侧的类型“c”不能更改。我们也没有任何方法可以更改该值,因为我们不知道它实际上是什么类型。我们所能做的就是别管它:
fmap f (Left rval) = Left rval
对于右侧,类型签名表明我们必须将类型为“a”的值更改为类型为“b”的值。第一个参数是一个函数来做这件事,所以我们使用这个函数和输入值来获得新的输出。将两者放在一起给出了完整的定义
instance Functor (Either c) where
fmap f (Right rval) = Right (f rval)
fmap f (Left lval) = Left lval
这里有一个更一般的原则在起作用,这就是为什么编写一个调整左侧的 Functor 实例是不可能的,至少在 Prelude 定义中是不可能的。从上面复制一些代码:
class Functor f where
fmap :: (a -> b) -> f a -> f b
instance Functor (Either c) where ...
即使我们在实例定义中有一个类型变量“c”,我们也不能在任何类方法中使用它,因为它没有在类定义中提及。所以不能写
leftMap :: (c -> d) -> Either c a -> Either d a
leftMap mapfunc (Left x) = Left (mapfunc x)
leftMap mapfunc (Right x) = Right x
instance Functor (Either c) where
--fmap :: (c -> d) -> Either c a -> Either d a
fmap = leftMap
leftMap 和 fmap 的结果是 now (Either d) a
。已(Either c)
更改为(Either d)
,但这是不允许的,因为无法在 Functor 类中表达它。为了表达这一点,您需要一个具有两个类型变量的类,例如
class BiFunctor f where
lMap :: (a -> b) -> f a c -> f b c
rMap :: (c -> d) -> f a c -> f a d
biMap :: (a -> b) -> (c -> d) -> f a c -> f b d
在这个类中,由于左右类型变量都在范围内,因此可以编写在任一(或两侧)操作的方法。
instance BiFunctor Either where
lMap = leftMap
rMap = rightMap --the same as the standard fmap definition
biMap fl fr e = rMap fr (lMap fl e)
尽管在实践中人们通常只为 BiFunctor 类编写“biMap”,如果需要左映射或右映射,则将“id”用于其他函数。
[1] 更准确地说,Left 值的类型为“c”,该函数需要一个“a”,但类型检查器无法统一这些类型,因为“c”类型不在类定义的范围内。