在Haskell中,(,)的Functor实例显然是
instance Functor (,) where
fmap f (a,b) = (a,f b)
这导致了不直观的事实:
> fmap (const 5) [1, 2]
[5,5]
> fmap (const 5) (1, 2)
(1,5)
现在,在我看来,使用这个定义会更好:
instance Functor (,) where
fmap f (a,b) = (f a,f b)
它会像这样工作:
> fmap (const 5) (1, 2)
(5,5)
为什么不喜欢这个?
答案 0 :(得分:4)
实例实际上不像你说的instance Functor (,)
。那不是很好的方式:
Prelude> :k Functor
Functor :: (* -> *) -> Constraint
Prelude> :k (,)
(,) :: * -> * -> *
即,(,)
采用两个类型参数(两个元组字段的类型)来构造元组类型,但Functor
类实际上是仅用于的类型构造函数一个参数,如
Prelude> :k []
[] :: * -> *
Prelude> :k IO
IO :: * -> *
那为什么还有一个仿函数实例呢?坦率地说,我认为这是 not 定义的实例之一,正是因为元组不对称而令人困惑。但是,可以实际定义,并且只有一种方法可以实现,因此标准库的这种选择肯定不是不合理的。诀窍是:你可以将(,)
构造函数部分应用于任何固定的左字段类型,然后为你提供单参数类型构造函数。例如
Prelude> :k (,) Int
(,) Int :: * -> *
所以你可以举个例子
instance Functor ((,) Int)
显然,它并不取决于左侧场具有哪种具体类型,所以你也可以做到这一点
instance Functor ((,) a)
在价值级Haskell中,本节将编写
instance Functor (a,)
与术语级别不同,部分应用程序仅为最左边的参数工作((,b)
部分实际上是\a -> (a,b)
的糖,但是没有类型-hvel lambda in Haskell),所以instance Functor ((,) a)
是唯一可能的实例。
要获得您要求的行为,即应用于两个/所有字段的功能,您需要两个字段实际上具有相同的类型。即,您需要一个只有一个参数的类型构造函数,并且只对该 value 构造函数的字段使用该类型两次。具有该行为的标准类型是V2
。
答案 1 :(得分:2)
Product
仿函数可以使用此行为:
Data.Functor.Product Data.Functor.Identity> fmap (+1) (Pair 5 6) :: Product Identity Identity Integer
Pair (Identity 6) (Identity 7)
正如您所看到的,Product
和(,)
之间的主要区别在于Pair
的两个元素都具有相同的叶类型(在本例中为Integer
) 。 (相关地:请注意,在上面的类型中,Integer
仅提及一次,而在(5,6)
类型中,Integer
被提及两次。)自{ {1}}可能包含完全不相关的类型,不能保证您可以以良好的方式将单个函数应用于这两个部分。