首页 > 解决方案 > 如何将此 if-then-else 构造更改为使用模式匹配或/和防护的构造?

问题描述

我得到了以下练习(其中一个链接在一起以漂亮地打印表格并在其中进行选择):

编写一个函数 select :: Field → Field → Table → Table,给定列名和字段值,仅从表中选择给定列中具有给定字段值的那些行。如果给定的列不存在于表中,则表应原样返回。(提示:使用函数 (!!)、elemIndex、filter 等等。)

最后得到了这个解决方案:

select :: Field -> Field -> Table -> Table
select column value table@(header:rows) =
    let i = fromMaybe (-1) (elemIndex column header)
    in  if i == (-1) then table
        else header : filter (\r -> r !! i == value) rows

虽然它的功能似乎完全正确——它有效——但有人告诉我,诸如此类的 if-then-else 结构是“糟糕的形式”,应该避免使用守卫(就像我使用 fromMaybe 使用模式匹配一​​样)。

我将如何通过模式匹配/防护将其更改为“更好”的样式?

标签: listfunctionhaskellpattern-matching

解决方案


fromMaybe在查看您的代码时,我立即看到的一个简单改进是,使用转换为 -1似乎毫无意义Nothing,然后只需检查if该值是否为 -1(无论使用 an 还是使用警卫都无关紧要)。为什么不Nothing首先检查呢?

我猜你可能已经被其他语言中的类似函数以这种方式引导,如果找不到索引,则 -1 作为人工值(有时称为“哨兵值”)返回,表示“找不到元素” - 但在 Haskell 中,Nothing这方面的沟通要好得多。

进一步Nothing可以进行模式匹配,因此if您可以使用case语句而不是使用或守卫。这会将您的功能变为:

select :: Field -> Field -> Table -> Table
select column value table@(header:rows) =
    case elemIndex column header of
          Nothing -> table
          Just i -> header : filter (\r -> r !! i == value) rows

在我看来,这比你原来的要好得多。

我仍然觉得它可以进一步改进 - 特别(!!)是在惯用的 Haskell 代码中很少使用,因为如果索引超出范围,它就有崩溃的风险。与其他语言的数组索引运算符相比,它的效率也相当低(因为 Haskell 列表是链表,而不是数组,并且要说第 100 个元素它必须通过前 99 个元素而不是能够直接“随机使用权”)。但是,鉴于您必须在这里工作,我看不出您如何才能真正避免这种情况。


推荐阅读