如何将这种 if-then-else 结构更改为使用模式匹配或/和防护的结构?

Lum*_*nar 1 haskell function list pattern-matching

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

\n
\n

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

\n
\n

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

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

虽然它的功能似乎完全正确 - 它有效 - 我被告知 if-then-else 结构(例如这些)是“坏形式”,应该避免使用防护(就像我使用 fromMaybe 使用模式匹配一​​样) )。

\n

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

\n

Rob*_*ond 5

在查看您的代码时,我立即看到的一个简单改进是,使用fromMaybe转换Nothing为 -1 似乎毫无意义,然后只需检查if该值是否为 -1(无论是否使用守卫都无关紧要)。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
Run Code Online (Sandbox Code Playgroud)

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

我仍然认为它可以进一步改进 - 特别(!!)是很少在惯用的 Haskell 代码中使用,因为如果索引越界,它就有崩溃的风险。与其他语言的数组索引运算符相比,它的效率也相当低(因为 Haskell 列表是链表,而不是数组,并且要获得第 100 个元素,它必须遍历前面的 99 个元素,而不是能够直接“随机”使用权”)。然而,考虑到你在这里必须处理的事情,我不知道如何真正避免这种情况。