为什么fmap不适用于元组?

时间:2018-04-01 12:06:45

标签: haskell tuples functor

在下面,我尝试在元组上使用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>

2 个答案:

答案 0 :(得分:7)

  

为什么fmap对[5-]元组不起作用?

因为还没有人将5元组Functor实例添加到 base 。如果你看一下the list of Functor instances provided by base,你会发现Functor ((,) a),对的实例,但不是大型元组的实例,包括Functor ((,,,,) a b c d),这就是你需要的。

后续问题当然是:为什么没有人将5元组Functor实例添加到 base 了?一个原因与必要性(或缺乏必然性)有关:在实践中,成对出现的次数远多于大型元组,并且随着元组变大,使用它们而不是非适合的非匿名类型变得越来越难以证明一个用例。既然如此,对较大元组的Functor实例的需求并不是那么大。

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

GHCi> fmap not (False, True)
(False,False)

有两个原因:

  1. 组件的类型可以不同,因此没有办法,例如fmap not ("foobar", True)可能会更改这两个组件。

  2. 在编写实例时无法翻转类型构造函数,因此例如对于作用于第一个组件的对,不能有Functor个实例(除非您使用a newtype wrapper,但除此之外)。

  3. 虽然这种行为可能看起来令人惊讶,但如果您将类型为(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 不提供TrifunctorTetrafunctor等等,因为缺乏必要,正如开头所讨论的那样。)

    在提供Functor个实例时,以相同的方式考虑更大的元组是合理的。事实上,为了保持一致,这些实例可能存在。但是,有些人非常不喜欢对的实例,导致proposals to add the instances for other tuples停滞。

    P.S。:或许值得一提的是,镜头库的(许多)用例之一是使用不是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)
    

答案 1 :(得分:4)

元组是特定数量的(可能)不同类型的值的分组。即使您考虑表达式(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)这样的五元组,它有这种类型:

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在评论中指出的那样,一个两元组 一个Functor,但它可能不会按照你期望的方式行事。 Haskell pair is a bit funky