The Occasional Occurence

Evolution of a Haskell Function

March 11, 2010 at 01:23 PM | categories: Python, Software, computing, General

I'm going through Real World Haskell trying to get a handle on the Haskell programming language. Python is my current language of choice, but I like to learn new programming languages too.

So last night I was going over the chapter that introduces 'let', 'where', 'case' and guards and I wanted to try them out. I contrived a simple situation where I thought I could use them.

I made some Contestants and wanted to see if they were "valid" for some definition of the word. I chose that they had to have a real value for age (not Missing) and be 30 years old or older (I'm pretty sure the game is called "Who Wants to Be a 30-year-old Computer Nerd?").

Here is my implementation.

-- my parameterized type
data Perhaps a = Is a
               | Missing
                 deriving (Show)

-- definition of a Contestant
data Contestant = Contestant {
                    name ::  String,
                    age  ::  Perhaps Int
                } deriving (Show)

-- some contestants
contestant1 = Contestant "Christian" (Is 31)
contestant2 = Contestant "Sarah" Missing
contestant3 = Contestant "Curtis" (Is 6)

-- the validation logic
contestantValid :: Contestant -> Bool
contestantValid c =
    case age c of
        Missing      -> False
        Is true_age  -> oldenough true_age
            where
                oldenough a
                    | a >= 30 = True
                    | a     <     30  = False

-- some code to print the results of running the validation against contestants
main = do
    print $ (contestant1, contestantValid contestant1)
    print $ (contestant2, contestantValid contestant2)
    print $ (contestant3, contestantValid contestant3)

I was pretty proud of myself. I used 'case', 'where' and guards (not to mention parameterized types - I reimplemented Maybe/Just/Nothing as Perhaps/Is/Missing).

But man, it seemed like a lot of code to do something simple.

As I was falling asleep I realized how I could simplify it. I could use pattern matching to easily handle the Missing case. It would also let me pull the age out of a Contestant with ease too.

Here is the contestantValid function updated to reflect that.

contestantValid :: Contestant -> Bool
contestantValid (Contestant _ Missing) = False
contestantValid (Contestant _ (Is age))
                                | age >= 30 = True
                                | age     <     30  = False

Quite a bit simpler. I patted myself on the back. It was less fancy than the first version, but at least I had some guards in there.

Then it hit me. I didn't need the guards.

contestantValid :: Contestant -> Bool
contestantValid (Contestant _ Missing) = False
contestantValid (Contestant _ (Is age)) = age >= 30

Well, so much for 'where', 'case' and guards. It sure is a boring function now, but I think the process of how I got there is interesting.

cw