Haskell 为什么fmap不';一个元组不行吗?

Haskell 为什么fmap不';一个元组不行吗?,haskell,tuples,functor,Haskell,Tuples,Functor,在下面的示例中,我尝试在元组上使用fmap,但这不起作用,尽管它适用于列表和仅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 constra

在下面的示例中,我尝试在元组上使用
fmap
,但这不起作用,尽管它适用于列表和
仅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>
Prelude>fmap(+3)(仅4)
只有7个
前奏曲>fmap(+3)[1,2,3,4]
[4,5,6,7]
序曲>fmap(+3)(10,11,12,13,14)
:38:1:错误:
*非类型变量参数
在约束中:函子((,,,,)a b1 c d)
(使用flexibleContext允许此操作)
*检查推断类型时
it::对于所有b1 c d b2。
(Num d,Num c,Num b1,Num a,Num b2,函子((,,,,)a b1 c d))=>
(a、b1、c、d、b2)
序曲>

元组是(可能)不同类型的特定数量的值的分组。即使你考虑表达式<代码>(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
可以是
单词
,依此类推

通常,您还可以编写一个五元组,如
(“foo”,42,True,“bar”,11)
,它具有以下类型:

Prelude> :t ("foo", 42, True, "bar", 11)
("foo", 42, True, "bar", 11)
  :: (Num t, Num t1) => ([Char], t1, Bool, [Char], t)
第一个元素是
[Char]
值(即
字符串
),下一个元素是某个数字,第三个元素是
Bool
,依此类推

不能将函数
+3
应用于所有这些元素。即使您认为它对字符串有意义(依我看,它不是),我希望您同意您不能将
3
添加到
True


由于它们的类型,元组通常不是
Functor
实例,因此不能对它们使用
fmap


正如Willem Van Onsem在评论中指出的那样,二元组是一个
函子
,但它可能不会以您期望的方式运行。这个

为什么fmap不适用于[5-]元组

因为还没有人将5元组
Functor
实例添加到base中。如果你看一下,你会发现
函子((,)a)
,成对的实例,而不是较大元组的实例,包括
函子((,,,)a b c d)
,这是你在这里需要的

接下来的问题当然是:为什么还没有人将5元组
Functor
实例添加到base中?其中一个原因与必要性(或缺乏必要性)有关:在实践中,成对出现的频率远远高于较大的元组,并且随着元组变大,越来越难以证明使用它们而不是适合自己用例的非匿名类型是正确的。因此,对于较大元组的
funcor
实例的需求并没有那么大

虽然您没有提到您期望的
函子((,,,)a b c d)
的行为,但值得注意的是,成对的
fmap
仅作用于第二个组件,而较大元组的实例类似地仅处理最后一个组件

GHCi> fmap not (False, True)
(False,False)
原因有两个:

  • 组件的类型可能不同,因此,例如,
    fmap not(“foobar”,True)
    无法更改这两个组件

  • 在编写实例时,无法翻转类型构造函数,因此,例如,对于作用于第一个组件的对,不能有
    Functor
    实例(除非使用,但这不在点上)

  • 虽然这种行为似乎令人惊讶,但如果您将类型为
    (a,b)
    的一对看作是一个
    b
    值,并附加了类型为
    a
    的注释(标签、标签、额外的东西,不管您怎么称呼它),那么这是完全合理的。在您更愿意将其视为一对可以独立修改的两个值的情况下,您可以求助于
    Bifunctor
    类:

    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不提供
    三函子
    四函子
    ,等等。)

    在给较大的元组提供
    Functor
    实例时,以同样的方式考虑较大的元组也是合理的;事实上,为了保持一致性,这些实例应该存在。然而,有些人确实非常不喜欢成对的例子,这导致了停顿

    附言:也许值得一提的是,镜头库的一个(许多)用例使用的不是
    Functor
    S的东西作为Functor。这为我们提供了一种方便的方法来查看
    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)
    

    因为元组的类型是
    (a,b,c)
    (可以泛化),所以如果你想让它成为
    函子的一个实例,你只能映射一个元素。一个有助于界定答案范围的问题:你希望从
    fmap(+3)(10,11,12,13,14)
    得到什么结果,这看起来像是从
    fmap(+3)(10,11)
    中得到的吗?顺便说一下,一个2元组是
    函子的一个实例--“由于它们的类型,元组通常不是
    函子的实例”--我不会这样说。虽然不是每个人都同意这一点,但我坚定地认为配对的
    函子
    实例是完全合理的。针对最后一个组件的较大元组的实例也同样合理;基本库不提供它们仅仅是因为,在实践中,它们比成对库需要的次数少得多(因此,有人建议为了一致性而添加缺少的实例)。@duplode确实如此,但我确实属于那种认为
    (,)
    函子
    实例不直观的阵营。我确实理解它为什么会出现,但我发现它含蓄而武断。同样,我理解将翻译应用于第二个元素的原因是由于部分应用,但我认为
    
    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)