如何将此 if-then-else 结构更改为使用模式匹配 or/and 守卫的结构?
How can I change this if-then-else construction to a construction that uses pattern matching or/and guards?
我得到了以下练习(link 一起漂亮地打印 table 并在其中进行选择的几个练习中的一个):
Write a function select :: Field → Field → Table → Table that given a column name and a field value, selects only those rows from the table that have the given field value in the given column. If the given column is not present in the table then the table should be returned unchanged. (Hint: use the functions (!!), elemIndex, filter and maybe.)
最后得到了这个解决方案:
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 结构是 'bad form' 并且应该避免使用守卫(我的应该使用 fromMaybe 使用模式匹配)。
我该如何将其更改为具有模式 matching/guards 的 'better' 样式?
替换
let i = fromMaybe (-1) (elemIndex column header)
in if i == (-1) then ... else ...
和
case elemIndex column header of
Nothing -> ... -- no index found
Just i -> ... -- index i found
您的原始代码存在“布尔盲性”问题:您已经拥有类型 Maybe Int
的有用值,它告诉您:
- 如果没有索引(
Nothing
)
- 如果有索引,它是什么 (
Just i
)
最后强调的部分很重要!在您的代码中,您努力 丢失该信息 ,将所有内容减少为布尔值,只告诉您:
- 如果没有索引(
True
)
- 如果有索引,没有别的(
False
)
除非确实需要,否则不要使用布尔值。许多语言必须使用 if someBooleanCondition
来有条件地分支。 Haskell 不必:我们有 case
和模式匹配,它结合了分支和从每个案例中提取信息(上面的 i
)。
我在查看您的代码时立即看到的一个简单改进是,使用 fromMaybe
将 Nothing
转换为 -1 似乎毫无意义,然后只需进行检查(无论是否使用 if
或与守卫无关)关于该值是否为 -1。为什么不首先检查 Nothing
?
我想您可能已经被其他语言中的类似函数引导到这种方式,如果找不到索引,则 -1 将作为人工值(有时称为“哨兵值”)返回,表示“未找到元素”- 但在 Haskell 中,Nothing
可以更好地传达这一点。
此外 Nothing
可以进行模式匹配,因此您可以使用 case
语句而不是使用 if
或守卫。这会将您的函数变成:
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 个而不是而不是能够直接“随机访问”)。但是,考虑到您必须在此处使用的内容,我看不出如何才能真正避免这种情况。
我得到了以下练习(link 一起漂亮地打印 table 并在其中进行选择的几个练习中的一个):
Write a function select :: Field → Field → Table → Table that given a column name and a field value, selects only those rows from the table that have the given field value in the given column. If the given column is not present in the table then the table should be returned unchanged. (Hint: use the functions (!!), elemIndex, filter and maybe.)
最后得到了这个解决方案:
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 结构是 'bad form' 并且应该避免使用守卫(我的应该使用 fromMaybe 使用模式匹配)。
我该如何将其更改为具有模式 matching/guards 的 'better' 样式?
替换
let i = fromMaybe (-1) (elemIndex column header)
in if i == (-1) then ... else ...
和
case elemIndex column header of
Nothing -> ... -- no index found
Just i -> ... -- index i found
您的原始代码存在“布尔盲性”问题:您已经拥有类型 Maybe Int
的有用值,它告诉您:
- 如果没有索引(
Nothing
) - 如果有索引,它是什么 (
Just i
)
最后强调的部分很重要!在您的代码中,您努力 丢失该信息 ,将所有内容减少为布尔值,只告诉您:
- 如果没有索引(
True
) - 如果有索引,没有别的(
False
)
除非确实需要,否则不要使用布尔值。许多语言必须使用 if someBooleanCondition
来有条件地分支。 Haskell 不必:我们有 case
和模式匹配,它结合了分支和从每个案例中提取信息(上面的 i
)。
我在查看您的代码时立即看到的一个简单改进是,使用 fromMaybe
将 Nothing
转换为 -1 似乎毫无意义,然后只需进行检查(无论是否使用 if
或与守卫无关)关于该值是否为 -1。为什么不首先检查 Nothing
?
我想您可能已经被其他语言中的类似函数引导到这种方式,如果找不到索引,则 -1 将作为人工值(有时称为“哨兵值”)返回,表示“未找到元素”- 但在 Haskell 中,Nothing
可以更好地传达这一点。
此外 Nothing
可以进行模式匹配,因此您可以使用 case
语句而不是使用 if
或守卫。这会将您的函数变成:
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 个而不是而不是能够直接“随机访问”)。但是,考虑到您必须在此处使用的内容,我看不出如何才能真正避免这种情况。