卫兵与if-then-else与Haskell中的案件


104

我有三个函数可以找到列表的第n个元素:

nthElement :: [a] -> Int -> Maybe a 
nthElement [] a = Nothing
nthElement (x:xs) a | a <= 0 = Nothing
                    | a == 1 = Just x
                    | a > 1 = nthElement xs (a-1)

nthElementIf :: [a] -> Int -> Maybe a
nthElementIf [] a = Nothing
nthElementIf (x:xs) a = if a <= 1
                        then if a <= 0 
                             then Nothing
                             else Just x -- a == 1
                        else nthElementIf xs (a-1)                           

nthElementCases :: [a] -> Int -> Maybe a
nthElementCases [] a = Nothing
nthElementCases (x:xs) a = case a <= 0 of
                             True -> Nothing
                             False -> case a == 1 of
                                        True -> Just x
                                        False -> nthElementCases xs (a-1)

我认为,第一个功能是最好的实现,因为它是最简洁的。但是,其他两种实现方式是否会使它们更受欢迎?通过扩展,您将如何在使用防护,if-then-else语句和案例之间进行选择?


5
case如果使用过的话,您可以折叠嵌套的语句case compare a 0 of LT -> ... | EQ -> ... | GT -> ...

5
@rampion:您的意思是case compare a 1 of ...
newacct

Answers:


121

从技术角度来看,这三个版本都是等效的。

话虽这么说,我对样式的经验法则是,如果您能像英语一样阅读它(读|为“ when”,| otherwise“ otherwise”,=“ is”或“ be”),那么您可能正在做某事对。

if..then..else用于当您有一个二进制条件或需要做出一个决定时。嵌套if..then..else表达式在Haskell中很少见,应该几乎总是使用警卫代替。

let absOfN =
  if n < 0 -- Single binary expression
  then -n
  else  n

如果每个if..then..else表达式都位于函数的顶层,则可以将其替换为保护,并且通常应优先使用此保护,因为这样可以更轻松地添加更多的案例:

abs n
  | n < 0     = -n
  | otherwise =  n

case..of适用于具有多个代码路径的情况,并且每个代码路径均以值的结构为指导 ,即通过模式匹配。您很少与True和比赛False

case mapping of
  Constant v -> const v
  Function f -> map f

防护对case..of表达式进行补充,这意味着,如果您需要根据值进行复杂的决策,请首先根据输入的结构进行决策,然后再根据结构中的值进行决策。

handle  ExitSuccess = return ()
handle (ExitFailure code)
  | code < 0  = putStrLn . ("internal error " ++) . show . abs $ code
  | otherwise = putStrLn . ("user error " ++)     . show       $ code

顺便说一句。作为样式提示,如果/ 之后的内容对于一行而言太长,或者总是出于其他原因而使用更多行,请始终在a =之前或a 之前换行:|=|

-- NO!
nthElement (x:xs) a | a <= 0 = Nothing
                    | a == 1 = Just x
                    | a > 1 = nthElement xs (a-1)

-- Much more compact! Look at those spaces we didn't waste!
nthElement (x:xs) a
  | a <= 0    = Nothing
  | a == 1    = Just x
  | otherwise = nthElement xs (a-1)

1
“你很少匹配TrueFalse”有没有在所有的任何场合,你会怎么做呢?毕竟,这种决定总是可以通过if和守卫来完成。
leftaboutabout

2
例如case (foo, bar, baz) of (True, False, False) -> ...
dflemstr 2012年

@dflemstr难道没有其他细微的区别吗,例如需要MonadPlus的后卫并返回monad的实例,而if-then-else则没有?但是我不确定。
J Fritsch

2
@JFritsch:该guard函数需要MonadPlus,但是我们在这里谈论的是与| test =子句中无关的警卫。
本·米尔伍德

感谢您提供的样式提示,现在已由疑惑确认。
trueadjustr

22

我知道这是关于显式递归函数的样式的问题,但是我建议最好的样式是找到一种方法来重用现有的递归函数。

nthElement xs n = guard (n > 0) >> listToMaybe (drop (n-1) xs)

2

这只是一个排序问题,但我认为它非常易读,并且具有与警卫相同的结构。

nthElement :: [a] -> Int -> Maybe a 
nthElement [] a = Nothing
nthElement (x:xs) a = if a  < 1 then Nothing else
                      if a == 1 then Just x
                      else nthElement xs (a-1)

最后一个不需要,如果由于没有其他可能性,函数也应该具有“万不得已的情况”,以防万一您遗漏任何东西。


4
可以使用案例警戒时,嵌套的if语句是一种反模式。
user76284
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.