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:

Haskell
data Status = Todo | Done
  deriving (Show, Eq)

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.

Haskell
statusLabel :: Status -> String

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.

Haskell
statusLabel :: Status -> String
statusLabel Todo = "not done"

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.

Haskell
statusLabel :: Status -> String
statusLabel Todo = "not done"
statusLabel Done = "completed"

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:

Haskell
type TaskId = Int
type Title = String

data Task = Task TaskId Title Status
  deriving (Show, Eq)

We want to write a function called describe that turns a full Task into a formatted String. We start with our signature:

Haskell
describe :: Task -> String

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.

Haskell
describe :: Task -> String
describe (Task tid title status) = 

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.

Haskell
describe :: Task -> String
describe (Task tid title status) =
  "#" ++ show tid ++ " " ++ title ++ " (" ++ statusLabel status ++ ")"

Notice how pattern matching allowed us to smoothly "unpack" the custom type into separate variables that we could easily combine.

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal