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