Thursday 1 October 2009

Dealing with Partial Functions

Functions can be either total (e.g. fully defined for all possible input values) or partial. Handling partial functions can be painful, for example if you pass an empty list to head then it'll terminate the program with an exception.

Partial functions are usually handled by either pattern matching being used to exclude cases where the function isn't defined e.g.


foo [] = 33 -- not safe to call head
foo xs = head xs -- safe to call head


Or using Maybe to indicate (via the type system) that the function might not return a value. The first exercise of chapter 4 of Real World Haskell asks you to rewrite some of the partial list functions so that they never fail.


safeHead :: [a] -> Maybe a
safeHead [] = Nothing
safeHead (x:_) = Just x

safeTail :: [a] -> Maybe [a]
safeTail [] = Nothing
safeTail (_:xs) = Just xs

safeLast :: [a] -> Maybe a
safeLast [] = Nothing
safeLast (y:[]) = Just y
safeLast (_:xs) = safeLast xs

safeInit :: [a] -> Maybe [a]
safeInit [] = Nothing
safeInit (x:[]) = Just []
safeInit (x:xs) = Just (x : fromJust(safeInit xs))


In order to be thorough, I thought I'd explore HUnit a bit more and write a few tests. The funny "~:" and "~=?" are just syntactic sugar for writing assertions. One area that confused me was the need to specify the type explicitly on the functions for undefined values. I'm not entirely sure why this has to be done...


testSafeHead = test [ "Safe head non-empty" ~: Just 1 ~=? safeHead [1]
, "Safe head null" ~: Nothing ~=? (safeHead [] :: Maybe Int) ]

testSafeTail = test [ "Safe tail empty" ~: Just [] ~=? safeTail [1]
, "Safe tail value" ~: Just [1] ~=? safeTail [2,1]
, "Safe tail nothing" ~: Nothing ~=? (safeTail [] :: Maybe [Int]) ]

testSafeLast = test [ "Safe last empty" ~: Nothing ~=? (safeLast [] :: Maybe Int)
, "Safe last 1 element" ~: Just 1 ~=? safeLast [1]
, "Safe last n element" ~: Just 1 ~=? safeLast [3,2,1] ]

testSafeInit = test [ "Safe Init empty" ~: Nothing ~=? (safeInit [] :: Maybe [Int])
, "Safe Init singleton" ~: Just [] ~=? safeInit [1]
, "Safe Init list" ~: Just [1,2] ~=? safeInit [1,2,3] ]

tests = TestList [TestLabel "safeHead" testSafeHead
,TestLabel "safeTail" testSafeTail
,TestLabel "safeLast" testSafeLast
,TestLabel "safeInit" testSafeInit]


You can run these tests within ghci with

*Main> runTestTT tests
Cases: 11 Tried: 11 Errors: 0 Failures: 0
Counts {cases = 11, tried = 11, errors = 0, failures = 0}


I think these tests would have been better written with QuickCheck where I could have compared them with the non-safe versions. But this'll do for now!