Modeling Absence with Maybe
The "Not Found" Problem and the Maybe Type
Welcome to Unit 4! In our previous lessons, you learned how to create descriptive type synonyms, define custom data types like our Task model, and use pattern matching to look inside those custom types. Now, we are going to tackle a very common real-world problem: searching for an item that might not exist.
Imagine looking up a task by its ID. What happens if you provide an ID for a task that is not in your list? In many programming languages, trying to access a missing item causes the program to crash or silently return an unsafe empty value (like null), leading to errors later on.
Haskell handles this safely by making "not found" an ordinary, expected value. It does this using a built-in type called Maybe. You can think of Maybe as a physical box.
- If the search is successful, the box contains exactly one item. We represent this using the word
Just, followed by the item (for example,Just task). - If the search fails, the box is completely empty. We represent this using the word
Nothing.
By using Maybe, we make absence explicit in the type of the function. That encourages us to handle both the success and failure cases. When we pattern match on both Just and Nothing, as we will in this lesson, our lookup can handle missing tasks without crashing.
Setting up a Helper Function
Before we write our safe search function, let's prepare a small helper function. As a reminder, here is the custom Task data type we built in previous lessons:
To search through a list of tasks, we will frequently need to check the ID of a given task. Instead of writing out the full pattern match every time, we can create a small helper function called taskId.
Because we only care about the ID, we use underscores _ to ignore the title and the status. This function takes a Task, looks inside it using pattern matching, and returns just the TaskId.
Writing a Safe Search Function
Now we are ready to write our findTask function. We want to provide a TaskId and a list of Task items, and we want it to return a Maybe Task (our safe box).
Let's start by defining the signature and the most basic scenario: an empty list.
If the list is empty [], it means we have run out of tasks to check. Therefore, the task we are looking for is definitely not there. We return Nothing. Notice that we use an underscore _ for the ID we are searching for, because if the list is empty, the ID we want does not matter.
Next, we handle the case where the list is not empty. We separate the list into the first task t and the rest of the list.
Here, we use guards (the | symbol) to check a condition:
- If the
taskIdof our current tasktequals thewantedID, we found a match! We safely place the task in our box by returningJust t. otherwise, if they do not match, we callfindTaskagain to keep searching through therestof the list.
