在Haskell中防守与if-then-else对比案例

Jas*_* Tu 98 haskell if-statement case

我有三个函数可以找到列表的第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)
Run Code Online (Sandbox Code Playgroud)

在我看来,第一个功能是最好的实现,因为它是最简洁的.但是有没有其他两个实现可以使它们更可取?通过扩展,您如何选择使用警卫,if-then-else语句和案例?

dfl*_*str 116

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

话虽这么说,我对风格的经验法则是,如果你能把它看成是英文(读|作"当",| otherwise"不然"和="是"或"是"),你可能正在做某事对.

if..then..else是因为你有一个二元条件,或者你需要做出一个单一的决定.嵌套 - if..then..else表达式在Haskell中非常罕见,并且几乎总是应该使用守卫.

let absOfN =
  if n < 0 -- Single binary expression
  then -n
  else  n
Run Code Online (Sandbox Code Playgroud)

if..then..else如果它位于函数的顶层,那么每个表达式都可以被一个守护者替换,这通常应该是首选的,因为你可以更容易地添加更多的情况:

abs n
  | n < 0     = -n
  | otherwise =  n
Run Code Online (Sandbox Code Playgroud)

case..of当你有多个代码路径时,每个代码路径都由一个值的结构引导 ,即通过模式匹配.你很少匹配TrueFalse.

case mapping of
  Constant v -> const v
  Function f -> map f
Run Code Online (Sandbox Code Playgroud)

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

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

BTW.作为样式提示,总是在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)
Run Code Online (Sandbox Code Playgroud)

  • 例如`case(foo,bar,baz)of(True,False,False) - > ...` (2认同)
  • @JFritsch:`guard`函数需要`MonadPlus`,但我们在这里谈论的是守卫,如`| test =`子句,与之无关. (2认同)

Dan*_*ner 22

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

nthElement xs n = guard (n > 0) >> listToMaybe (drop (n-1) xs)
Run Code Online (Sandbox Code Playgroud)


小智 5

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

\n
nthElement :: [a] -> Int -> Maybe a\nnthElement (x:_) 1  = Just x\nnthElement (_:xs) i = nthElement xs (i - 1)\nnthElement _ _      = Nothing  -- index is out of bounds\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n