【问题标题】:Why does this '@' usage in Haskell fail to encompass the whole list?为什么 Haskell 中的这个“@”用法不能包含整个列表?
【发布时间】:2021-08-21 20:03:37
【问题描述】:

我正在研究 99 个 Haskell 问题并尝试使用这段代码来解决第二个问题,这需要返回数组的倒数第二个元素:

myPen :: [a] -> a
myPen [] = error "Empty lists have no penultimate element."
myPen list@(_:xs)
  | length list == 1  = error "Lists size 1 have no penultimate element."
  | length xs == 2    = head xs
  | otherwise         = myPen xs

这段代码可以编译,但是当给定输入时

myPen [1,2]

它会产生错误

*** Exception: Lists size 1 have no penultimate element.
CallStack (from HasCallStack):
  error, called at prob1_10.hs:16:25 in main:Main

据我了解,@ 在这种情况下应该允许引用整个列表,但它似乎只引用了xs 或列表的尾部。

This 似乎支持它应该包括xxs 的论点。

我的问题有两个:

1.) 为什么会这样? 2.) 引用整个列表的正确方法是什么?

【问题讨论】:

  • 第一个守卫都不是真的,因为length list2,而length xs1。注意xs是列表的tail,所以列表没有第一个元素。
  • “倒数第二个”的意思是“倒数第二个”,因此是myPen。不过,只是为了清楚起见而进行了编辑。
  • length 在这种情况下(在警卫中)应不惜一切代价避免。它完全不必要地破坏了惰性,并在有限列表上引入了二次行为,并在无限列表上引入了分歧。
  • @WillNess 我会进一步说,初学者永远不应该永远使用length!!head
  • @leftaroundabout 是的,...除非在实现一些计数时。或实际上采取了一些东西。 :) 那么,我们应该说,“几乎总是”

标签: haskell


【解决方案1】:

在这种情况下发生的实际上是length list == 2,而是length xs == 1,所以第一个或第二个后卫都不会匹配。将采用 otherwise 保护并在长度为 1 的 xs 上递归调用该函数,因此将采用第一个保护并打印错误消息。您可以通过编写来修复它:

myPen :: [a] -> a
myPen [] = error "Empty lists have no penultimate element."
myPen list@(_:xs)
  | length list == 1  = error "Lists size 1 have no penultimate element."
  | length list == 2  = head list
  | otherwise         = myPen xs

但在 Haskell 中,使用模式匹配而不是 length 函数通常更常见,如下所示:

myPen :: [a] -> a
myPen []     = error "Empty lists have no penultimate element."
myPen [_]    = error "Lists size 1 have no penultimate element."
myPen [x,_]  = x
myPen (_:xs) = myPen xs

【讨论】:

  • 这是有道理的。并感谢您展示了更哈斯克尔式的写作方式。
  • @Chaos_Is_Harmony,确实,用模式匹配来做这件事要高效得多!尝试myPen [1..100000] 与这些实现中的每一个,您可能会感到惊讶。
【解决方案2】:

Haskell 在概念上从上到下进行评估。这意味着它首先查看列表是否为空。然后看list的长度,也就是2,这样守卫就不会开火了。然后查看length xs 是否为2,但事实并非如此,因为xs 是列表的尾部,所以[2] 的长度为1

最终它使用myPen [2] 进行递归调用,然后第一个守卫(length list)确实是一个,因此引发了错误。

因此,您可能希望通过以下方式检查整个列表:

myPen :: [a] -> a
myPen [] = error "Empty lists have no penultimate element."
myPen list@(_:xs)
  | length list == 1  = error "Lists size 1 have no penultimate element."
  | length list == 2  = head list  -- check length of list
  | otherwise         = myPen xs

但是建议使用length,因为它需要线性时间来确定长度,并且对于具有无限长度的列表,它会陷入无限循环。你最好使用模式匹配:

myPen :: [a] -> a
myPen [] = error "Empty lists have no penultimate element."
myPen [_] = error "Lists size 1 have no penultimate element."
myPen [_,x] = x
myPen (x:xs) = pen xs

【讨论】:

  • 请注意,使用这些更改调用head xs 意味着程序仍然不正确。
  • 感谢关于无限循环的警告。
  • 如果你的目标是计算一个无限列表的倒数第二个元素,不管你是否使用length
  • @Joe:是的,没错。但是通常在 Haskell 程序中使用 length 通常是“丑陋的”:它在这里将程序转换为具有二次时间的程序,并且如果另一个指针仍然指向列表的头部,它通常会炸毁内存,所以如果我们保留对列表头部的引用,它通常会避免列表融合。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-02
  • 2021-10-30
  • 2012-02-13
  • 1970-01-01
  • 1970-01-01
  • 2017-01-11
相关资源
最近更新 更多