Guard vs. if-then-else vs. Haskell Cases

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

In my opinion, the first function is the best implementation, because it is the most concise. But is there anything about the other two implementations that will make them preferable? And expanding, how can you choose between using guards, if-then-else statements, and cases?

+85
if-statement haskell case
Feb 19 2018-12-12T00:
source share
3 answers

From a technical point of view, all three versions are equivalent.

As the saying goes, my rule for styles is that if you can read it as if it were English (read | as "when", | otherwise as "otherwise" and = as "is", or "be "), you are probably doing something right.

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

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

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

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

case..of is when you have several code paths, and each code path is controlled by a value structure, that is, by way of pattern matching. You are very rare on True and False .

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

The guards complement case..of expressions, which means that if you need to make complex decisions depending on the value, first make decisions depending on the structure of your input, and then make decisions about 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 hint, always create a new line after = or before | if the material after = / | too long for one line, or uses more lines for another 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) 
+97
Feb 19 2018-12-12T00:
source share

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

 nthElement xs n = guard (n > 0) >> listToMaybe (drop (n-1) xs) 
+21
Feb 19 2018-12-12T00:
source share

This is only a matter of order, but I consider it very readable and has the same structure as the 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 latter is not necessary, and if there are no other possibilities, the functions should also have an β€œextreme case” if you have not missed anything.

+1
May 22 '14 at 21:19
source share



All Articles