4
\$\begingroup\$

Background

This is my first experience with functional programming and totally self-study. I just have 2-3 days' worth experience in Haskell. Now let's continue with actual code.


For Problem 2 of 99 haskell problems I wrote this function

module Functions 
(
lastButOne
) where

lastButOne :: [a] -> a
lastButOne [a, b] = a
lastButOne (_:xs) = lastButOne xs
lastButOne a = error "Not Supported"

Is this code ok from functional programming perspective? I am open to criticism from any perspective.

EDIT 1

Code after @Pedro Rodrigues suggestions

lastButOne :: [a] -> a
lastButOne [a, _] = a
lastButOne (_:xs) = lastButOne xs
lastButOne _ = error "lastButOne requires a list with at least 2 members"
\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

You have the right idea, I just want to make a few remarks:

  1. 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.
  2. 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
\$\endgroup\$
4
  • \$\begingroup\$ Starting from An even safer... I didn't get anything. Can you give an example what do you mean. Also how is this a variant of Problem 2? I added an edited code. Is this what you meant? \$\endgroup\$ Jan 9, 2014 at 3:38
  • \$\begingroup\$ @AseemBansal Forget what I said about being a variant. Don't know what I was thinking... sorry for the confusion. I've already fixed my answer. \$\endgroup\$ Jan 9, 2014 at 11:56
  • \$\begingroup\$ The idea of Maybe is good but getting error every time Couldn't match expected type 'a' with actual type Maybe 'a' in In an equation for 'lastButOne': lastButOne [a, _] = a. Any ideas why? \$\endgroup\$ Jan 9, 2014 at 16:26
  • \$\begingroup\$ @AseemBansal I've added an example implementation to my answer. \$\endgroup\$ Jan 9, 2014 at 21:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.