为什么 (,) 的 Functor 实例映射到第二个值?
Why is the Functor instance for (,) mapping on the second value?
在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)
为什么不是这样?
此行为可从 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
被提及 两次 。 ) 由于 (,)
可能包含完全不相关的类型,因此不能保证您可以以类型正确的方式将单个函数应用于两个部分。
实例其实不是你说的instance Functor (,)
。那可不是好心人:
Prelude> :k Functor
Functor :: (* -> *) -> Constraint
Prelude> :k (,)
(,) :: * -> * -> *
即 (,)
采用两个类型参数(两个元组字段的类型)来构造元组类型,但 Functor
class 实际上用于类型构造函数只需要一个参数,比如
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)
的糖分,但是 Haskell 中没有类型级别的 lambda,所以 instance Functor ((,) a)
是这里唯一可能的实例。
要获得您要求的行为,即应用于 both/all 字段的函数,您需要两个字段实际上具有相同的类型。即,您需要一个只有一个参数开头的类型构造函数,并且只需将该类型用于其 value 构造函数的字段两次。具有该行为的标准类型是 V2
.
在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)
为什么不是这样?
此行为可从 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
被提及 两次 。 ) 由于 (,)
可能包含完全不相关的类型,因此不能保证您可以以类型正确的方式将单个函数应用于两个部分。
实例其实不是你说的instance Functor (,)
。那可不是好心人:
Prelude> :k Functor
Functor :: (* -> *) -> Constraint
Prelude> :k (,)
(,) :: * -> * -> *
即 (,)
采用两个类型参数(两个元组字段的类型)来构造元组类型,但 Functor
class 实际上用于类型构造函数只需要一个参数,比如
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)
的糖分,但是 Haskell 中没有类型级别的 lambda,所以 instance Functor ((,) a)
是这里唯一可能的实例。
要获得您要求的行为,即应用于 both/all 字段的函数,您需要两个字段实际上具有相同的类型。即,您需要一个只有一个参数开头的类型构造函数,并且只需将该类型用于其 value 构造函数的字段两次。具有该行为的标准类型是 V2
.