我只是想知道我在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) =
答案 0 :(得分:9)
我的一般经验法则是:
==
检查。通过递归,您通常会检查基本情况。因此,如果您的基本案例是简单的==
检查,那么请使用模式匹配。
所以我通常会这样做:
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)
模式匹配是为了让你的生活更轻松,imho,所以最后你应该做对你有意义的事情。如果您与团队合作,那么请对团队做有意义的事情。
[更新]
对于你的具体情况,我会做这样的事情:
f _ [] = []
f 0 _ = ...
f y (x:xs) = ...
模式匹配,如警卫,从上到下,在与输入匹配的第一个定义处停止。我使用下划线符号表示对于第一个模式匹配,我不关心y
参数是什么,对于第二个模式匹配,我不关心列表参数是什么(尽管,如果你确实使用该计算中的列表,那么你不应该使用下划线)。因为它仍然相当简单==
- 就像检查一样,我个人坚持使用模式匹配。
但我认为这是个人偏好的问题;你的代码是完全可读和正确的。如果我没有弄错的话,在编译代码时,守护和模式匹配最终都会变成case语句。
答案 1 :(得分:4)
一个简单的规则
讨论
从根本上说,它取决于您希望做的测试以防止递归。如果它是对数据类型结构的测试,请使用模式匹配,因为它比冗余测试更有效。
对于您的示例,整数上的模式匹配显然更清晰,更有效:
units 0 = "zero"
units 1 = "one"
对于任何数据类型的递归调用都是如此,您可以通过数据的形状区分案例。
现在,如果你有更复杂的逻辑条件,那么警卫就会有意义。
答案 2 :(得分:2)
对此没有真正严格的规则,这就是为什么你得到的答案有点朦胧。有些决定很简单,比如[]
上的模式匹配,而不是用f xs | null xs = ...
守护,或者天堂禁止,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 * ...
在f1
中,单独的模式强调递归有两个基本情况。在f2
中,警卫强调0对于某些计算来说只是一个特殊情况(大部分由calc
完成,在守卫的两个分支共享的where
子句中定义)并且不会改变计算的结构。
答案 3 :(得分:2)
@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甚至为匹配的案例共同编写了字符串。
答案 4 :(得分:2)
到目前为止,答案中没有提到模式匹配的优势,这对我来说是最重要的:安全地实现全部功能的能力。
进行模式匹配时,您可以安全地访问对象的内部结构,而不必担心此对象是其他内容。如果您忘记了某些模式,编译器可以向您发出警告(遗憾的是,此警告在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