Haskell 为什么对于包含2个元素的元组,length返回1,而对于包含更多元素的元组,length返回错误?

Haskell 为什么对于包含2个元素的元组,length返回1,而对于包含更多元素的元组,length返回错误?,haskell,Haskell,我正在用《基本数据类型》一书研究Haskell,在第4章“基本数据类型”的末尾,我遇到了一些让我困惑的事情。这本书提到了一个函数length,并说它适用于列表s。这一切都很好,但当我尝试使用各种元组的length函数时,我所看到的让我困惑: 首先,让我们看看长度的类型: :t length length :: Foldable t => t a -> Int 好的,我把上面的理解为“取一个可折叠的,为了方便起见,我认为它是一个列表,并返回一个Int,即列表中的元素数。”因此我的第一

我正在用《基本数据类型》一书研究Haskell,在第4章“基本数据类型”的末尾,我遇到了一些让我困惑的事情。这本书提到了一个函数
length
,并说它适用于
列表
s。这一切都很好,但当我尝试使用各种
元组的
length
函数时,我所看到的让我困惑:

首先,让我们看看
长度的类型:

:t length
length :: Foldable t => t a -> Int
好的,我把上面的理解为“取一个可折叠的,为了方便起见,我认为它是一个列表,并返回一个Int,即列表中的元素数。”因此我的第一个困惑是:为什么下面的方法可以工作:

length (1, 1)
1
因为对我来说,好像我刚刚把一个包含两个元素的元组传递给
length
,它返回了1。元组是列表吗?元组是可折叠的吗?当然,为什么
1

现在我更进一步:

length (1, 1, 1)

<interactive>:6:1:
    No instance for (Foldable ((,,) t0 t1))
      arising from a use of ‘length’
    In the expression: length (1, 1, 1)
    In an equation for ‘it’: it = length (1, 1, 1)

<interactive>:6:9:
    No instance for (Num t0) arising from the literal ‘1’
    The type variable ‘t0’ is ambiguous
    Note: there are several potential instances:
      instance Num Integer -- Defined in ‘GHC.Num’
      instance Num Double -- Defined in ‘GHC.Float’
      instance Num Float -- Defined in ‘GHC.Float’
      ...plus two others
    In the expression: 1
    In the first argument of ‘length’, namely ‘(1, 1, 1)’
    In the expression: length (1, 1, 1)

<interactive>:6:12:
    No instance for (Num t1) arising from the literal ‘1’
    The type variable ‘t1’ is ambiguous
    Note: there are several potential instances:
      instance Num Integer -- Defined in ‘GHC.Num’
      instance Num Double -- Defined in ‘GHC.Float’
      instance Num Float -- Defined in ‘GHC.Float’
      ...plus two others
    In the expression: 1
    In the first argument of ‘length’, namely ‘(1, 1, 1)’
    In the expression: length (1, 1, 1)

我在上面观察到的行为有什么好的解释吗?我是否误读了长度的类型?还是在幕后发生了什么

你遇到了一个Haskell cause célèbre,它引发了很多讨论和切齿

基本上,为了(提供
长度的typeclass
)的目的,2元组不被认为是两个元素的容器,而是一个元素的容器,并伴有一些上下文

您可以从任何
可折叠a
中提取类型为
a
的元素列表。请注意,对于2元组,
Foldable
的类型变量是元组的第二个元素的类型变量,它可以不同于第一个元素的类型

如果你有一个
('c',2):(Char,Int)
元组,那么在这种情况下你不能提取两个
Int
s就不神秘了!但是,当类型相同时,它就会变得混乱

至于
length(1::Int,1::Int,1::Int)
失败的原因,3元组没有定义可折叠的
实例,但为了一致性,它们可能应该有一个实例。3元组的长度也应该是1

顺便说一句,
Identity
函子,可以看作是一种1元组,也是可折叠的,当然长度也是1


元组的
可折叠
实例是否应该存在?我认为赞成“是”的基本哲学是,我们称之为“充分”。如果一个类型可以以定义良好的合法方式成为typeclass的实例,那么它应该拥有该实例。即使它看起来不是很有用,在某些情况下,可能会令人困惑。

我喜欢danidiaz的答案,因为它提供了关于元组的可折叠实例如何工作以及直观含义的高级直觉。然而,关于它的机制似乎仍然有一些混乱;因此,在这个回答中,我将重点关注“幕后”部分。所讨论的
可折叠
实例的全文如下:

instance Foldable ((,) a) where
    foldMap f (_, y) = f y
    foldr f z (_, y) = f y z
您已经可以从这个实例中看到,在所有
Foldable
方法中,每个元组的第一部分都被完全忽略。然而,为了完成这幅图,我们需要查看
最小值
长度
的定义。由于此实例不包括
最小值
长度
的定义,因此我们应该查看这些的默认定义。
Foldable
的类声明如下所示(省略了不相关的位):

现在,让我们扩展这些定义,看看它们从何而来

length (a, b)
= { definition of length }
foldl' (\c _ -> c+1) 0 (a, b)
= { definition of foldl' }
foldr (\x k z -> k $! (\c _ -> c+1) z x) id (a, b) 0
= { definition of foldr }
(\x k z -> k $! (\c _ -> c+1) z x) b id 0
= { beta reduction }
id $! (\c _ -> c+1) 0 b
= { id $! e = e }
(\c _ -> c+1) 0 b
= { beta reduction }
1
请注意,无论我们为
a
b
插入什么,结论都成立。现在让我们做
最小值
。出于我们的目的,我们将用
()
替换
()
——唯一的区别是效率,对于这一特定的推理过程,我们并不关心效率

minimum (a, b)
= { definition of minimum }
( fromMaybe (error "minimum: empty structure")
. getMin
. foldMap (Min . Just)
) (a, b)
= { definition of (.) }
( fromMaybe (error "minimum: empty structure")
. getMin
) (foldMap (Min . Just) (a, b))
= { definition of foldMap }
( fromMaybe (error "minimum: empty structure")
. getMin
) ((Min . Just) b)
= { definition of (.) }
fromMaybe (error "minimum: empty structure")
(getMin (Min (Just b)))
= { definition of getMin }
fromMaybe (error "minimum: empty structure") (Just b)
= { definition of fromMaybe }
b

是的,这是一个常见的抱怨-这是因为
Foldable
只处理元组的最后一部分,而且事实上有一个
Foldable
的例子,并且仅适用于那些-我建议你还是照原样去做-如果你想找到大量关于这方面的讨论-这里有一个更混乱的问题!我没想到会发生这种事。但是,困惑依然存在:(我试图通过检查
minimum(1,2)
返回
2
minimum(2,1)
返回
1
,和
minimum('c',2)
返回
2
@EmreSevinç
可折叠的
基本上是“可转换为列表”的事实来解释你写的内容。)typeclass。它的所有操作都可以使用使用
toList
Foldable
值获得的列表上的相同结果来执行。出于效率原因,它们直接在
Foldable
中定义(例如,如果一个容器跟踪自己的长度,这比遍历整个列表更有效)。此外,如果一个容器有多个类型参数(如2元组do和映射),用于
Foldable
的话,它的“元素类型”将始终是它的最后一个参数。@EmreSevinç“为什么
toList
只使用第二个元素”(我想我已经在链接的帖子中解释过了),或者“为什么
length
Foldable
实例生成的列表进行操作”–这基本上是一个必要的问题,以保持语义一致。例如,您可能希望计算某个容器中所有数字的平均值。如果您以
sum xs/length xs
的方式进行计算,但求和只覆盖一个元素,而长度为两个,则会得到一个完整的垃圾结果。(不可否认,无论如何,这是一件相当幼稚的事情。)是的。我认为最好不要给
(a,)
一个
可折叠的
实例,“如果一个类型可以成为一个
class Foldable t where
    length :: t a -> Int
    length = foldl' (\c _ -> c+1) 0

    foldl' :: (b -> a -> b) -> b -> t a -> b
    foldl' f z0 xs = foldr f' id xs z0
      where f' x k z = k $! f z x

    minimum :: forall a . Ord a => t a -> a
    minimum = fromMaybe (error "minimum: empty structure") .
       getMin . foldMap (Min #. (Just :: a -> Maybe a))
length (a, b)
= { definition of length }
foldl' (\c _ -> c+1) 0 (a, b)
= { definition of foldl' }
foldr (\x k z -> k $! (\c _ -> c+1) z x) id (a, b) 0
= { definition of foldr }
(\x k z -> k $! (\c _ -> c+1) z x) b id 0
= { beta reduction }
id $! (\c _ -> c+1) 0 b
= { id $! e = e }
(\c _ -> c+1) 0 b
= { beta reduction }
1
minimum (a, b)
= { definition of minimum }
( fromMaybe (error "minimum: empty structure")
. getMin
. foldMap (Min . Just)
) (a, b)
= { definition of (.) }
( fromMaybe (error "minimum: empty structure")
. getMin
) (foldMap (Min . Just) (a, b))
= { definition of foldMap }
( fromMaybe (error "minimum: empty structure")
. getMin
) ((Min . Just) b)
= { definition of (.) }
fromMaybe (error "minimum: empty structure")
(getMin (Min (Just b)))
= { definition of getMin }
fromMaybe (error "minimum: empty structure") (Just b)
= { definition of fromMaybe }
b