【问题标题】:Haskell Declare empty list, but which is actually not empty?Haskell 声明空列表,但实际上哪个不是空的?
【发布时间】:2017-12-09 01:03:03
【问题描述】:

我是 Haskell 的新手,正在玩一些游戏。我用警卫创建了一个递归函数。请参阅下面的功能:

filterAge :: [Person] -> [String]
filterAge (x:xs)
 | (x:xs) == []                        = []
 | (getAge x) < 30 || (getAge x) > 40  = [] ++ filterAge xs
 | otherwise                           = [getName x] ++ filterAge xs

我有一个由 10 个人创建的数据集,我在此方法中使用。当我尝试这个功能时,它给了所有合适的人,但之后它得到了一个非详尽模式错误: ["Lise","Jaap","Elle","Ebba"*** Exception: D:\...:(44,1)-(47,77): Non-exhaustive patterns in function filterAge

我发现它永远不会到达第一个守卫。所以我玩了一下,发现了一些非常奇怪的东西(在我看来):

*Main> let (x:xs) = []
*Main> (x:xs) == []
False

现在我的主要问题是:为什么(x:xs) == [] 返回 False?

如果有人对我有更好的方法来完成该功能,那就太好了,但这不是很重要。

提前致谢!

编辑

感谢 Willem Van Onsem 和 Lambda.xy.x,我的问题得到了快速解答。这导致以下功能完美运行:

filterAge :: [Person] -> [String]
filterAge []                           = []
filterAge (x:xs)
 | (getAge x) < 30 || (getAge x) > 40  = [] ++ filterAge xs
 | otherwise                           = [getName x] ++ filterAge xs

但要获得最佳版本,您必须查看 Willem Van Onsem 的答案。

【问题讨论】:

  • (x:xs) == [] 永远不会成功。因为(x:xs) 是一个包含至少一个元素的列表。
  • 谢谢,这有帮助。所以我想我应该解决它?不过不知道该怎么做,因为我的函数有点需要 (x:xs) ..
  • 为了扩展答案,一个列表有两个构造函数:[]_ : _,最外层构造函数[] 的列表永远不能有: 作为最外层构造函数。
  • 您已经在filterAge (x:xs) 的定义中进行了模式匹配,并在那里排除了空列表。最简单的解决方法是在上面添加定义filterAge []。或者,您可以定义 filterAge xs 并匹配任意列表。
  • @ lambda.xy.x 这行得通!我完全忽略了这种可能性,我之前已经使用过一点,但没有想到这个功能。非常感谢!

标签: list haskell filter pattern-matching lazy-evaluation


【解决方案1】:

列表定义为:

data [] a = [] | a : [a]

所以列表有两个构造函数:[] 空列表,(x:xs) 一个具有一个元素的构造函数,以及一个可以存储一个元素的 tail任意数量(零个或多个)剩余元素。

因此(x:xs) 是一个包含至少一个元素的列表:xxs 可以是一个空列表(因为它的类型为[a]),但x 的类型为a,所以这是列表的“head”。您的let 语句适用于模式匹配,由于空列表无法与(x:xs) 匹配,因此它总是会失败。

另一个含义是你的第一后卫永远不能开火。为了解决这个问题,您应该为空列表实现一个单独的案例。喜欢:

filterAge :: [Person] -> [String]
filterAge []     = [] -- empty list case
filterAge (x:xs) -- first guard dropped
    | (getAge x) < 30 || (getAge x) > 40  = [] ++ filterAge xs
    | otherwise                           = [getName x] ++ filterAge xs

请注意,我们在第二个子句中删除了第一个保护,因为我们知道它总是会失败,因此检查它(可能)只会消耗 CPU 周期。

还有一些部分我们可以优化:

  1. 我们调用getAge两次,没用,我们可以使用where子句来优化这个;
  2. [] ++ somelist 就是 somelist:在一个空列表之后追加会导致该列表;和
  3. [element] ++ somelistelement : somelist,因为现在我们直接使用列表构造函数。

所以我们的filterAge可以改写成:

filterAge :: [Person] -> [String]
filterAge []     = [] -- empty list case
filterAge (x:xs) | age < 30 || age > 40  = filterAge xs
                 | otherwise             = getName x : filterAge xs
    where age = getAge x

请注意,如果您使用 -Wincomplete-patterns 标志编译(或启动解释器)(警告对于不完整的模式),Haskell 将自动警告您您的函数定义不完整,并且存在是您尚未为其定义子句的输入模式。

【讨论】:

  • 非常感谢您的回答!它有很大帮助,不仅可以回答我的问题,还可以分析我的代码并使其变得更好。同时解释为什么它会更好。
【解决方案2】:

要回答您的“主要”问题,

*Main&gt; let (<strong>x</strong>:<strong>xs</strong>) = [] -- <em>(1)</em>

表示,xxs的值是必需的,匹配(x:xs)[]使用 em> 生成的绑定。匹配将永远成功,任何此类尝试将总是导致模式匹配失败错误

那为什么不呢?

*Main&gt; <b>(</b>x<b>:</b>xs<b>)</b> == [] -- <em>(2)</em>

现在这意味着,尝试比较 (x:xs) []。两者都是列表;比较列表涉及到模式匹配顶层结构,然后递归地比较组件,成功 -- 返回False 失败(实际上并非失败)。因此,(_:_)[] 之间的匹配被尝试,但失败,导致False 值的立即返回作为比较的结果。

请注意,绑定到 xxs 在任何时候都被请求;因此没有错误,因为 (1) 中的(x:xs)[] 的匹配从未触发。

【讨论】:

  • 感谢您的解释,您的回答让我更加了解。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-26
  • 2012-08-03
相关资源
最近更新 更多