快速浏览GHCi中的以下互动环节:
Prelude> import Control.Applicative Prelude Control.Applicative> (+1) <$> [1,2] [2,3] Prelude Control.Applicative> (+1) <$> (1,2) (1,3)
我想<$>
关于对的行为是有充分理由的,但到目前为止我找不到一个,所以:
为什么<$>
(或fmap
)定义为仅对一对的第二个成员起作用而不是对两个值都有效?
答案 0 :(得分:15)
<$>
(又名fmap
)是Functor类的成员,如下所示:
class Functor f where
fmap :: (a -> b) -> f a -> f b
所以f必须是带有一个类型参数的参数化类型。列表是一种此类型,当以前缀形式[]
编写时([] a
与[a]
相同)。所以列表的实例是:
instance Functor [] where
-- fmap :: (a -> b) -> [] a -> [] b
fmap = map
对也可以用前缀形式书写:(,) a b
与(a, b)
相同。因此,如果我们想要一个涉及对的Functor实例,那么让我们考虑一下我们的工作。我们无法声明instance Functor (,)
,因为对构造函数(,)
有两种类型 - 它们可以是不同的类型!我们可以做的是为(,) a
声明一个实例 - 这是一种只需要一种类型的类型:
instance Functor ( (,) a ) where
-- fmap :: (b -> c) -> (,) a b -> (,) a c
fmap f (x, y) = (x, f y)
希望你能看到fmap的定义是我们能给出的唯一合理的定义。关于函数实例对一对中的第二项进行操作的原因的答案是第二项的类型在列表中排在最后!我们不能轻易地声明对一对中的第一项进行操作的仿函数实例。顺便提一下,这可以推广到更大的元组,例如:四元组(,,,) a b c d
(又名(a, b, c, d)
)也可以在最后一项上有Functor
个实例:
instance Functor ( (,,,) a b c) where
-- fmap :: (d -> e) -> (,,,) a b c d -> (,,,) a b c e
fmap f (p, q, r, s) = (p, q, r, f s)
希望有助于解释一切!
答案 1 :(得分:3)
考虑Functor
类型类的定义:
class Functor f where
fmap :: (a -> b) -> f a -> f b
显然,f有种* -> *
。因此,您只能为类型为* -> *
的数据类型声明实例。你可以做的是做一些像这样的事情:
instance Functor (,) where
fmap :: (a -> b) -> (,) a -> (,) b
这适用于部分应用的元组,并且真的很不方便。所以一个人定义了这样的实例:
instance Functor ((,) a) where
fmap :: (b -> c) -> (,) a b -> (,) a c
fmap f (x,y) = (x,f y)
简而言之:在普通的Haskell 98中我不可能(虽然我相信这有一个语法扩展)将实例定义为
你可以做的是定义你自己的元组:
data T a = T a a
instance Functor T where
fmap f (T a b) = T (f a) (f b)
然后你可以做任何你喜欢的事情。你知道,因为那种是* -> *
而不是* -> * -> *
,所以一切都没问题。
答案 2 :(得分:2)
我猜,元组不需要是同质的,我的意思是两种类型都可以不同。如果你想要一个同类元组,你可以使用一个列表,然后fmap就可以了。
您希望(+1) ("Hello", 2)
如何工作?
Prelude> import Control.Applicative
Prelude Control.Applicative> (+1) <$> ("hello",2)
("hello",3)
这只是工作,但两种类型相同时没有特殊行为。 顺便说一句,我不知道为什么不使用第二个值而不是第一个值,但无论如何你只能使用一个值。