为什么 fmap 不适用于元组?
Why fmap doesn't work for a tuple?
在下面,我尝试在元组上使用 fmap
,但这没有用,尽管它适用于列表和 Just 4
:
Prelude> fmap (+3) (Just 4)
Just 7
Prelude> fmap (+3) [1,2,3,4]
[4,5,6,7]
Prelude> fmap (+3) (10,11,12,13,14)
<interactive>:38:1: error:
* Non type-variable argument
in the constraint: Functor ((,,,,) a b1 c d)
(Use FlexibleContexts to permit this)
* When checking the inferred type
it :: forall a b1 c d b2.
(Num d, Num c, Num b1, Num a, Num b2, Functor ((,,,,) a b1 c d)) =>
(a, b1, c, d, b2)
Prelude>
元组是一组特定数量的值(可能)不同类型。即使考虑表达式 (10,11,12,13,14)
,每个元素也可能具有不同的类型:
Prelude> :t (10,11,12,13,14)
(10,11,12,13,14)
:: (Num t, Num t1, Num t2, Num t3, Num t4) => (t4, t3, t2, t1, t)
例如,表达式 10
可能是 Int
,而 11
可能是 Word
,依此类推。
一般来说,你也可以写一个像("foo", 42, True, "bar", 11)
这样的five-tuple,它有这样的类型:
Prelude> :t ("foo", 42, True, "bar", 11)
("foo", 42, True, "bar", 11)
:: (Num t, Num t1) => ([Char], t1, Bool, [Char], t)
第一个元素是 [Char]
值(即 String
),下一个元素是某个数字,第三个元素是 Bool
,依此类推。
您不能将函数 +3
应用于所有这些元素。即使您认为它对字符串有意义(IMO,它不是),我希望您同意不能将 3
添加到 True
.
由于它们的类型,元组通常不是 Functor
个实例,因此,您不能对它们使用 fmap
。
正如 Willem Van Onsem 在评论中指出的那样,two-tuple 是 和 Functor
,但它可能不会按照您预期的方式运行。 Haskell pair is a bit funky.
Why fmap doesn't work for a [5-]tuple?
因为还没有人将 5 元组 Functor
实例添加到 base。如果你看一下 the list of Functor
instances provided by base,你会发现 Functor ((,) a)
,对的实例,但不是更大元组的实例,包括 Functor ((,,,,) a b c d)
,这是你在这里需要的。
后续问题当然是:为什么还没有人将 5 元组 Functor
实例添加到 base?一个原因与必要性(或缺乏必要性)有关:在实践中,对比较大的元组更频繁地出现,并且随着元组变得越来越大,越来越难以证明使用它们而不是 non-anonymous 类型是合理的适合一个人的用例。既然如此,对更大元组的 Functor
个实例的需求并不是那么大。
虽然您没有提及您期望 Functor ((,,,,) a b c d)
的行为,但值得注意的是 fmap
对仅作用于第二个组件,而较大元组的实例类似地只处理与最后一个组件。
GHCi> fmap not (False, True)
(False,False)
两个原因是:
组件的类型可以不同,没办法fmap not ("foobar", True)
可能会改变这两个组件。
在编写实例时无法翻转类型构造函数,例如对于作用于第一个组件的对,不能有 Functor
实例(除非您使用 a newtype wrapper,但这不是重点)。
虽然这种行为可能看起来令人惊讶,但如果您将类型为 (a, b)
的对视为带有注释的 b
值,这是完全合理的(标签,标签,额外的东西 - 但是你喜欢称它为 a
类型。在您宁愿将其视为可以独立修改的一对两个值的情况下,您可以求助于 Bifunctor
class:
GHCi> import Data.Bifunctor
GHCi> first reverse ("foobar", True)
("raboof",True)
GHCi> second not ("foobar", True)
("foobar",False)
GHCi> bimap reverse not ("foobar", True)
("raboof",False)
(base 不提供 Trifunctor
、Tetrafunctor
等,因为不需要,如开头所述。)
在给更大的元组 Functor
实例时,以相同的方式对待它们同样合理;事实上,为了保持一致性,这些实例可以说应该存在。但是,有些人非常不喜欢配对实例,这导致 proposals to add the instances for other tuples 停滞不前。
P.S.: 也许值得一提的是 lens 库的(许多)用例之一正在使用 Functor
s 作为函子。这为我们提供了一种方便的方式来查看 Functor
和(如果那是一回事)Pentafunctor
实例将如何处理 5 元组:
GHCi> import Control.Lens
GHCi> over _5 (+3) (10,11,12,13,14)
(10,11,12,13,17)
GHCi> over _4 (+3) (10,11,12,13,14)
(10,11,12,16,14)
GHCi> over _3 (+3) (10,11,12,13,14)
(10,11,15,13,14)
GHCi> over _2 (+3) (10,11,12,13,14)
(10,14,12,13,14)
GHCi> over _1 (+3) (10,11,12,13,14)
(13,11,12,13,14)
甚至还有映射所有组件的方法...
GHCi> over both (+3) (13,14)
(16,17)
GHCi> over each (+3) (10,11,12,13,14)
(13,14,15,16,17)
...不过,毫不奇怪,他们要求所有组件都具有相同的类型:
GHCi> over each (+3) (True,11,12,13,14)
<interactive>:9:12: error:
* No instance for (Num Bool) arising from a use of `+'
* In the second argument of `over', namely `(+ 3)'
In the expression: over each (+ 3) (True, 11, 12, 13, 14)
In an equation for `it':
it = over each (+ 3) (True, 11, 12, 13, 14)
GHCi> :set -XPartialTypeSignatures
GHCi> :set -fno-warn-partial-type-signatures
GHCi> :t \f -> over each f :: (_,_,_,_,_) -> _
\f -> over each f :: (_,_,_,_,_) -> _
:: (w -> b5) -> (w, w, w, w, w) -> (b5, b5, b5, b5, b5)
在下面,我尝试在元组上使用 fmap
,但这没有用,尽管它适用于列表和 Just 4
:
Prelude> fmap (+3) (Just 4)
Just 7
Prelude> fmap (+3) [1,2,3,4]
[4,5,6,7]
Prelude> fmap (+3) (10,11,12,13,14)
<interactive>:38:1: error:
* Non type-variable argument
in the constraint: Functor ((,,,,) a b1 c d)
(Use FlexibleContexts to permit this)
* When checking the inferred type
it :: forall a b1 c d b2.
(Num d, Num c, Num b1, Num a, Num b2, Functor ((,,,,) a b1 c d)) =>
(a, b1, c, d, b2)
Prelude>
元组是一组特定数量的值(可能)不同类型。即使考虑表达式 (10,11,12,13,14)
,每个元素也可能具有不同的类型:
Prelude> :t (10,11,12,13,14)
(10,11,12,13,14)
:: (Num t, Num t1, Num t2, Num t3, Num t4) => (t4, t3, t2, t1, t)
例如,表达式 10
可能是 Int
,而 11
可能是 Word
,依此类推。
一般来说,你也可以写一个像("foo", 42, True, "bar", 11)
这样的five-tuple,它有这样的类型:
Prelude> :t ("foo", 42, True, "bar", 11)
("foo", 42, True, "bar", 11)
:: (Num t, Num t1) => ([Char], t1, Bool, [Char], t)
第一个元素是 [Char]
值(即 String
),下一个元素是某个数字,第三个元素是 Bool
,依此类推。
您不能将函数 +3
应用于所有这些元素。即使您认为它对字符串有意义(IMO,它不是),我希望您同意不能将 3
添加到 True
.
由于它们的类型,元组通常不是 Functor
个实例,因此,您不能对它们使用 fmap
。
正如 Willem Van Onsem 在评论中指出的那样,two-tuple 是 和 Functor
,但它可能不会按照您预期的方式运行。 Haskell pair is a bit funky.
Why fmap doesn't work for a [5-]tuple?
因为还没有人将 5 元组 Functor
实例添加到 base。如果你看一下 the list of Functor
instances provided by base,你会发现 Functor ((,) a)
,对的实例,但不是更大元组的实例,包括 Functor ((,,,,) a b c d)
,这是你在这里需要的。
后续问题当然是:为什么还没有人将 5 元组 Functor
实例添加到 base?一个原因与必要性(或缺乏必要性)有关:在实践中,对比较大的元组更频繁地出现,并且随着元组变得越来越大,越来越难以证明使用它们而不是 non-anonymous 类型是合理的适合一个人的用例。既然如此,对更大元组的 Functor
个实例的需求并不是那么大。
虽然您没有提及您期望 Functor ((,,,,) a b c d)
的行为,但值得注意的是 fmap
对仅作用于第二个组件,而较大元组的实例类似地只处理与最后一个组件。
GHCi> fmap not (False, True)
(False,False)
两个原因是:
组件的类型可以不同,没办法
fmap not ("foobar", True)
可能会改变这两个组件。在编写实例时无法翻转类型构造函数,例如对于作用于第一个组件的对,不能有
Functor
实例(除非您使用 a newtype wrapper,但这不是重点)。
虽然这种行为可能看起来令人惊讶,但如果您将类型为 (a, b)
的对视为带有注释的 b
值,这是完全合理的(标签,标签,额外的东西 - 但是你喜欢称它为 a
类型。在您宁愿将其视为可以独立修改的一对两个值的情况下,您可以求助于 Bifunctor
class:
GHCi> import Data.Bifunctor
GHCi> first reverse ("foobar", True)
("raboof",True)
GHCi> second not ("foobar", True)
("foobar",False)
GHCi> bimap reverse not ("foobar", True)
("raboof",False)
(base 不提供 Trifunctor
、Tetrafunctor
等,因为不需要,如开头所述。)
在给更大的元组 Functor
实例时,以相同的方式对待它们同样合理;事实上,为了保持一致性,这些实例可以说应该存在。但是,有些人非常不喜欢配对实例,这导致 proposals to add the instances for other tuples 停滞不前。
P.S.: 也许值得一提的是 lens 库的(许多)用例之一正在使用 Functor
s 作为函子。这为我们提供了一种方便的方式来查看 Functor
和(如果那是一回事)Pentafunctor
实例将如何处理 5 元组:
GHCi> import Control.Lens
GHCi> over _5 (+3) (10,11,12,13,14)
(10,11,12,13,17)
GHCi> over _4 (+3) (10,11,12,13,14)
(10,11,12,16,14)
GHCi> over _3 (+3) (10,11,12,13,14)
(10,11,15,13,14)
GHCi> over _2 (+3) (10,11,12,13,14)
(10,14,12,13,14)
GHCi> over _1 (+3) (10,11,12,13,14)
(13,11,12,13,14)
甚至还有映射所有组件的方法...
GHCi> over both (+3) (13,14)
(16,17)
GHCi> over each (+3) (10,11,12,13,14)
(13,14,15,16,17)
...不过,毫不奇怪,他们要求所有组件都具有相同的类型:
GHCi> over each (+3) (True,11,12,13,14)
<interactive>:9:12: error:
* No instance for (Num Bool) arising from a use of `+'
* In the second argument of `over', namely `(+ 3)'
In the expression: over each (+ 3) (True, 11, 12, 13, 14)
In an equation for `it':
it = over each (+ 3) (True, 11, 12, 13, 14)
GHCi> :set -XPartialTypeSignatures
GHCi> :set -fno-warn-partial-type-signatures
GHCi> :t \f -> over each f :: (_,_,_,_,_) -> _
\f -> over each f :: (_,_,_,_,_) -> _
:: (w -> b5) -> (w, w, w, w, w) -> (b5, b5, b5, b5, b5)