You have the right idea, I just want to make a few remarks:
- You could have used
_
in the first and the last clause of your function in place of the ignored arguments. It might help readability to know right away that an argument is being ignored.
- You're explicitly throwing an error on invalid input which is fine. A good error message could be more helpful to the caller than just getting a pattern match fail. However it wouldn't have shocked me if you had omitted that last line. An even safer solution is having the function return
Just a
when the input is valid and Nothing
when it's not, but for performance/convenience's sake many partial functions out there don't do that.
EDIT: To elaborate more on the safer solution point I mentioned earlier: lastButOne
has type lastButOne :: [a] -> a
. However this type fails to convey that lastButOne
is a partial function, and therefore may fail on certain inputs. It's very easy for a caller to overlook that he needs to watch out for invalid inputs. However, had you defined lastButOne
as lastButOne :: [a] -> Maybe a
instead, then the fact that it might not produce a result for some inputs, is directly encoded in the type of the function, thus making it type safer. Any caller of lastButOne
couldn't possibly overlook that he needs to handle failure.
In case you haven't heard of Maybe
yet, it's a data structure that is used to elegantly express a computation that may fail. Examples:
my_div :: Integer -> Integer -> Maybe Integer
my_div _ 0 = Nothing
my_div a b = Just (div a b)
lastButOne :: [a] -> Maybe a
lastButOne [a, _] = Just a
lastButOne (_:xs) = lastButOne xs
lastButOne _ = Nothing