如何解决在Haskell中使用防护比使用模式进行递归功能更好吗?
| 我只是想知道我在Haskell中布置的递归函数。使用防护比使用模式进行递归功能通常更好吗? 我只是不确定最佳布局是什么,但我确实知道在定义如下函数时模式会更好:units :: Int -> String
units 0 = \"zero\"
units 1 = \"one\"
相对于
units n
| n == 0 = \"zero\"
| n == 1 = \"one\"
我只是不确定,到底是相同还是不同。
只是不太确定术语:我使用的是这样的东西:
f y [] = []
f y (x:xs)
| y == 0 = ......
| otherwise = ......
还是会更好?
f y [] = []
f 0 (x:xs) =
f y (x:xs) =
解决方法
我的一般经验法则是这样的:
当防护罩很简单地通过“ѭ4”检验时,请使用模式匹配。
使用递归时,通常会检查基本情况。因此,如果您的基本情况是简单的“ 4”格检查,请使用模式匹配。
因此,我通常会这样做:
map f [] = []
map f (x:xs) = f x : map f xs
而不是这样(null
只是检查列表是否为空。基本上是== []
):
map f xs | null xs = []
| otherwise = f (head xs) : map f (tail xs)
模式匹配旨在使您的生活更轻松,恕我直言,因此最终您应该做对您有意义的事情。如果您与一个小组一起工作,请对小组有意义。
[更新]
对于您的特殊情况,我会执行以下操作:
f _ [] = []
f 0 _ = ...
f y (x:xs) = ...
模式匹配(如守卫)从上到下下降,在与输入匹配的第一个定义处停止。我使用下划线符号表示对于第一个模式匹配,我不在乎y
参数是什么,对于第二个模式匹配,我不在乎list参数是什么(尽管在该计算中使用列表,则不应使用下划线)。由于它仍然是相当简单的类似于“ 4”字样的检查,因此我个人坚持使用模式匹配。
但是我认为这是个人喜好问题;您的代码是完全可读且正确的。如果我没记错的话,则在编译代码时,防护和模式匹配最终都会变成case语句。
,一个简单的规则
如果要递归数据结构,请使用模式匹配
如果递归条件比较复杂,请使用防护措施。
讨论区
从根本上讲,这取决于您希望进行递归保护的测试。如果这是对数据类型的结构的测试,请使用模式匹配,因为它比冗余的相等测试更有效。
对于您的示例,整数上的模式匹配显然更清洁,更高效:
units 0 = \"zero\"
units 1 = \"one\"
对于任何数据类型的递归调用也是如此,您可以通过数据的形状区分大小写。
现在,如果您具有更复杂的逻辑条件,那么警卫就有意义。
,对此并没有严格的规定,这就是为什么您得到的答案有些朦胧的原因。有些决定很容易,例如在ѭ14上进行模式匹配而不是用ѭ15进行保护,或者天堂禁止f xs | length xs == 0 = ...
,这在多种方面都非常糟糕。但是,当没有引人注目的实际问题时,请使用使代码更清晰的任何一种。
作为示例,请考虑以下功能(实际上并没有做任何有用的工作,仅用作说明):
f1 _ [] = []
f1 0 (x:xs) = [[x],xs]
f1 y (x:xs) = [x] : f1 (y - 1) xs
f2 _ [] = []
f2 y (x:xs) | y == 0 = calc 1 : f2 (- x) xs
| otherwise = calc (1 / y) : f2 (y * x) xs
where calc z = x * ...
在《 18》中,单独的模式强调了递归有两个基本情况。在ѭ19中,警卫队强调,0只是某些计算的特殊情况(其中大部分由calc
完成,在in警卫两个分支共享的where
子句中定义),并且不会改变计算的结构。
,@Dan是正确的:这基本上是个人喜好,不会影响所生成的代码。该模块:
module Test where
units :: Int -> String
units 0 = \"zero\"
units 1 = \"one\"
unitGuarded :: Int -> String
unitGuarded n
| n == 0 = \"zero\"
| n == 1 = \"one\"
产生了以下核心:
Test.units =
\\ (ds_dkU :: GHC.Types.Int) ->
case ds_dkU of _ { GHC.Types.I# ds1_dkV ->
case ds1_dkV of _ {
__DEFAULT -> Test.units3;
0 -> Test.unitGuarded2;
1 -> Test.unitGuarded1
}
}
Test.unitGuarded =
\\ (n_abw :: GHC.Types.Int) ->
case n_abw of _ { GHC.Types.I# x_ald ->
case x_ald of _ {
__DEFAULT -> Test.unitGuarded3;
0 -> Test.unitGuarded2;
1 -> Test.unitGuarded1
}
}
完全相同,不同的默认情况除外,这两种情况都是模式匹配错误。 GHC甚至为匹配的案例共享了字符串。
,到目前为止的答案没有提到模式匹配的优点,这对我来说最重要:安全实现全部功能的能力。
在进行模式匹配时,您可以安全地访问对象的内部结构,而不必担心该对象是其他东西。万一您忘记了某些模式,编译器会警告您(不幸的是,默认情况下,此警告在GHC中处于关闭状态)。
例如,编写此代码时:
map f xs | null xs = []
| otherwise = f (head xs) : map f (tail xs)
您被迫使用非总计函数head
和tail
,从而冒着程序生命的危险。如果您在保护条件上犯了一个错误,编译器将无济于事。
另一方面,如果在模式匹配中出错,则编译器会根据错误的严重程度为您提供错误或警告。
一些例子:
-- compiles,crashes in runtime
map f xs | not (null xs) = []
| otherwise = f (head xs) : map f (tail xs)
-- does not have any way to compile
map f (h:t) = []
map f [] = f h : map f t
-- does not give any warnings
map f xs = f (head xs) : map f (tail xs)
-- can give a warning of non-exhaustive pattern match
map f (h:t) = f h : map f t
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。