【问题标题】:Why does length return 1 for a tuple with 2 elements, and gives an error for a tuple with more elements?为什么长度为具有 2 个元素的元组返回 1,并为具有更多元素的元组给出错误?
【发布时间】:2016-04-06 19:38:17
【问题描述】:

我正在使用“Haskell Programming from First Principles”这本书来学习 Haskell,在第 4 章“基本数据类型”快结束时,我遇到了一些让我感到困惑的事情。这本书提到了一个函数length,并说它适用于Listss。一切都很好,但是当我用各种Tuples 尝试这个length 函数时,我所看到的让我感到困惑:

首先我们看看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)

再试一次:

length (1::Int, 1::Int, 1::Int)

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

但以下工作:

length (1::Int, 1::Int)
1

对我在上面观察到的行为有什么好的解释吗?我误读了length 的类型吗?还是在幕后发生了其他事情?

【问题讨论】:

标签: haskell


【解决方案1】:

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

基本上,对于Foldable(提供length 的类型类)而言,2-tuples 不被视为包含两个元素的容器,而是由 一个 元素伴随着一些元素的容器上下文。

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

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

至于为什么length (1::Int, 1::Int, 1::Int) 失败,三元组没有定义Foldable 实例,但为了保持一致性,也许他们应该有一个。三元组的长度也为 1。

顺便说一句,Identity 函子,可以被认为是一种 1 元组,也是 Foldable,当然长度也是 1。

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

【讨论】:

  • 我试图通过检查minimum (1, 2) 返回2minimum (2,1) 返回1minimum ('c', 2) 返回2 的事实来解释您写的内容。跨度>
  • @EmreSevinç Foldable 基本上是“可转换为列表”类型类。它的所有操作都可以在使用toListFoldable 值获得的列表上以相同的结果执行。出于效率原因,它们直接在Foldable 中定义(例如,如果容器跟踪自己的长度,这比遍历列表的完整长度更有效)。此外,如果一个容器有多个类型参数(如 2 元组和映射)用于Foldable,它的“元素类型”将始终是它的最后一个参数。
  • @EmreSevinç 你的意思是“为什么toList 只使用第二个元素”(我想我已经在链接的帖子中解释过),或者“为什么length 对列表进行操作由Foldable 实例生成”——这基本上是一个必要的问题,以保持语义一致。例如,您可能想要计算某个容器中所有数字的平均值。如果您以sum xs / length xs 执行此操作,但总和仅超过一个元素而长度为 2,您将得到一个完整的垃圾结果。 (诚​​然,无论如何,这将是一件相当幼稚的事情。)
  • 是的。我认为最好永远不要给(a,) Foldable 实例,
  • "如果一个类型可以以一种定义明确、合法的方式成为一个类型类的实例,它应该有那个实例。即使它看起来不是很有用,在某些情况下,可能令人困惑。”我完全不同意这一点。是什么让您认为这是个好主意?
【解决方案2】:

我喜欢 danidiaz 的回答,因为它提供了关于元组的 Foldable 实例如何工作及其直观含义的高级直觉。然而,它的机制似乎仍然有些混乱;所以在这个答案中,我将专注于“幕后”位。有问题的Foldable 实例的全文是available online,如下所示:

instance Foldable ((,) a) where
    foldMap f (_, y) = f y
    foldr f z (_, y) = f y z

您已经可以从这个实例中看到每个元组的第一部分在所有Foldable 方法中完全忽略。然而,为了完成这幅图,我们需要查看minimumlength 的定义。由于此实例不包括 minimumlength 的定义,我们应该查看这些的默认定义。 Foldable 的类声明如下所示(省略了不相关的位):

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

请注意,无论我们为ab 插入什么,结论都成立。现在让我们做minimum。出于我们的目的,我们将用(.) 替换(#.)——唯一的区别是效率,对于这种特定的推理方式,我们并不关心。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-07-20
    • 2021-09-17
    • 2019-10-17
    • 1970-01-01
    • 1970-01-01
    • 2018-12-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多