Pattern Matching on Constructors
Introduction: Making Custom Data Useful
Welcome to Unit 3 of our seven-unit course! Now that you are settling into Haskell, it is time to take the next step.
In our previous lesson, you learned how to model information by defining your own custom data types. We created a Status type to track if something is done and a Task type to hold the details of a job. However, simply storing data is not enough. To make a working task manager, we need a way to look inside those custom types and use the data they hold.
As a quick reminder, you have already used pattern matching to check specific numbers or pull apart lists and tuples. In this lesson, we are going to apply that same pattern matching concept to our custom constructors. By the end of this lesson, you will be able to transform your raw custom data into nicely formatted text that a user can easily read.
Matching Exact Constructors (The Status Type)
Let us start with a simple custom type. In our last lesson, we defined a Status type that looks like this:
We want to write a function that takes a Status and returns a human-readable label as a String. We will call it statusLabel. First, we write the function signature.
Now, we need to define what the function actually does. With pattern matching, we can write a separate equation for each constructor. Let us start by handling the Todo case.
Here, we are telling Haskell: "If the status provided is exactly Todo, return the string "not done"."
However, our function is not finished yet. If we pass a Done status to this function, Haskell will not know what to do and will cause an error. To fix this, we add a second equation right below the first one to handle the Done case.
By writing one equation for Todo and one for Done, we have covered every possible choice our Status type allows. In programming, a function that covers every possible input is called a total function. This is a very safe and common way to write functions in Haskell!
Extracting Data from Constructors (The Task Type)
Matching exact names like Todo and Done is great, but what if our custom type holds data? Let us recall our Task type and the type aliases we created for it:
We want to write a function called describe that turns a full Task into a formatted String. We start with our signature:
To get the data out of the Task, we use pattern matching on the left side of the equals sign. We write the constructor name (Task) followed by variable names for the data we want to extract. We must put parentheses around this pattern so Haskell knows it is a single task being passed in.
In this pattern, tid will hold the TaskId, title will hold the Title, and status will hold the Status. Now that we have extracted the inner variables, we can use them on the right side of the equals sign to build our sentence.
We will use the ++ operator to join strings together. Since tid is an integer, we use the show function to turn it into a string. Since status is our custom Status type, we use our new statusLabel function to turn it into a readable label.
Notice how pattern matching allowed us to smoothly "unpack" the custom type into separate variables that we could easily combine.
