Guards vs. if-then-else vs. cases in Haskell

前端 未结 3 957
迷失自我
迷失自我 2021-01-29 18:09

I have three functions that find the nth element of a list:

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


        
相关标签:
3条回答
  • 2021-01-29 18:56

    From a technical standpoint, all three versions are equivalent.

    That being said, my rule of thumb for styles is that if you can read it as if it was English (read | as "when", | otherwise as "otherwise" and = as "is" or "be"), you're probably doing something right.

    if..then..else is for when you have one binary condition, or one single decision you need to make. Nested if..then..else-expressions are very uncommon in Haskell, and guards should almost always be used instead.

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

    Every if..then..else expression can be replaced by a guard if it is at the top level of a function, and this should generally be preferred, since you can add more cases more easily then:

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

    case..of is for when you have multiple code paths, and every code path is guided by the structure of a value, i.e. via pattern matching. You very seldom match on True and False.

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

    Guards complement case..of expressions, meaning that if you need to make complicated decisions depending on a value, first make decisions depending on the structure of your input, and then make decisions on the values in the structure.

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

    BTW. As a style tip, always make a newline after a = or before a | if the stuff after the =/| is too long for one line, or uses more lines for some other reason:

    -- 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)
    
    0 讨论(0)
  • 2021-01-29 19:10

    This is just a matter of ordering but I think its very readable and has the same structure as guards.

    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)
    

    The last else doesn't need and if since there is no other possibilities, also functions should have "last resort case" in case you missed anything.

    0 讨论(0)
  • 2021-01-29 19:12

    I know this is question about style for explicitly recursive functions, but I would suggest that the best style is finding a way to reuse existing recursive functions instead.

    nthElement xs n = guard (n > 0) >> listToMaybe (drop (n-1) xs)
    
    0 讨论(0)
提交回复
热议问题