Haskell 中的 Guards 与 if-then-else 与 case

问题描述 投票:0回答:4

我有三个函数可以查找列表的第 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 语句和 case 之间进行选择?

haskell if-statement case
4个回答
151
投票

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

话虽这么说,我对样式的经验法则是,如果你能像英语一样阅读它(将

|
读作“何时”,
| otherwise
读作“否则”,
=
读作“是”或“是”),你可能做对了。

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

Guards 补充了

case..of
表达式,这意味着如果您需要根据值做出复杂的决策,first 根据输入的结构做出决策,然后 then 根据结构中的值做出决策。

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

顺便说一句。 作为样式提示,如果

=
/
|
之后的内容对于一行来说太长,或者在某些情况下使用更多行,请始终在
=
之后或
|
之前创建换行符其他原因:

-- 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)

24
投票

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

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

5
投票

虽然所有三个实现都产生了正确的结果,但 GHC(截至 2021 年)抱怨模式匹配并不详尽 - 就可能的模式隐藏在 Guard/if/case 后面而言,这是正确的。考虑这个实现,它比它们三个更简洁,而且避免了非详尽模式警告:

nthElement :: [a] -> Int -> Maybe a
nthElement (x:_) 1  = Just x
nthElement (_:xs) i = nthElement xs (i - 1)
nthElement _ _      = Nothing  -- index is out of bounds

最后一个模式匹配所有内容,因此需要低于前两个模式可能成功的匹配。


4
投票

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

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)

最后一个 else 不需要 if,因为没有其他可能性,函数也应该有“最后的手段”,以防你错过任何东西。

© www.soinside.com 2019 - 2024. All rights reserved.