Haskell Project: Show, Compare, and Filter

Now that we have a few basic types we should start working on making their interaction nicer. It is pretty important to be able to print and compare data types so we will start there. Once we are able to compare, we should be able to filter lists of data types.

If you didn't read the previous post catch up on [Stack and Data Types][1]. Next post introduces [Unit Testing][5].

About Data Structures

In the previous post we created a few very basic data types using the `type` and `data` keywords. There are three data type keywords and we will quickly cover them here.

Data Type Keyword: type

`type` is the simplest data type keyword. It creates an alias to an already existing type. In the code above we have created a type `Name` that is really just a `String`. `Age` is really just an `Int`. Both types can be interchanged with their aliased types and can use functions defined for their originating types.

Since we aren't doing anything extremely interesting with `Name`, having it defined as an alias to `String` makes a lot of sense since its mostly for making the code more readable.

Since `length` works on `Foldable` class types and `String` is a `Foldable` then our alias works with length as well. (I'll explain type classes later in the post.) Our simple little method to calculate the length of a name is all just syntactic sugar to make the code more readable.

One thing to note though. When creating data types using `type`, type safty is enforced only allowing the casting to work from dirived type to base type. For example, lets say we create two types, `GivenName` and `SirName`:

And we create a method that prints the full name with the following signature:

Even though both data types are aliases to `String` you cannot pass a `SirName` for the argument where a `GivenName` is expected.

Data Type Keyword: data

The next data type keyword we used was `data`. This keyword describes a structure with

- One or more constructors

- Each constructor contains zero or more fields

In the example we will create a `Person` data type that has a single constructor and takes in a `Name` and an `Age`.

Another example is to create a data type that represents a department of an business. There are two different types of departments: Empty and Staffed. Both department types have a name but only one has a list of employees.

Functions can now interact with the type `Department` but have two different versions to handle.

Using pattern matching we are able to create two different forms of the `staffCountInDepartment` function which handles an `EmptyDepartment` differently than a `StaffedDepartment`.

Here we also see the difference between the data type and the constructor strings. In the function signature we use `Department` since that is the type. In the implementation we use the constructor names `EmptyDepartment` and `StaffedDepartment`. In our `Person` example since there is only one constructor it is acceptable to have both the data type and constructor to have the same name.

# Side Note: Pattern Matching and Undscore

Note in the pattern matching you can either fill the field with a name or use `_` to ignore it. Haskell expects all arguments defined in functions and test expressions to be used somewhere in the code. In situations where the value is not needed, using the `_` name tells Haskell that it can ignore the fact that you aren't using the value.

Data Type Keyword: newtype

The one keyword not used yet is `newtype`. It is similar to `data` and `type`, just with a few odd rules.

- Must create a new constructor (unlike `type`)

- Can only have one constructor (unlike `data`)

- Can only have a single field (like `type`)

- Does not associate functions using similar types (unlike `type`)

From these rules `newtype` really is a *new* type. It contains the data of the type supplied but that is where the association between the new and old types end.

Here you see that even though `DeptName` is based on `String` the function `length` does not operate as expected. We would need to create our own new method to calculate the length.

Here we are deconstructing the new type, extracting the `String` from it and calling another method. While this seems a bit cumbersome, the benefit here is that it would not be possible to pass in `Name` or any other string type to this function.

From the error we see that the function expected `DeptName` but instead was passed `Name`.

One good example of `newtype` is taking a query string and sanitizing it before processing. As a pointless example, lets say our language parser only used capitalized words.

Even though both types look the same, you cannot pass a `SanitizedString` into `sanitize`. If we used `type` this restriction wouldn't be possible.

Showing with the Show Type Class

A nice feature of haskell is being able to derive functionality from class types using the `deriving` keyword. Starting with our current implementation of `Date` we derive `Show` and get the following output.

That is nice for basic development and debugging but what if we wanted to change the default way the `Date` type was shown. We could match the format expected by todo.txt, `YYYY-MM-DD`. We just need to overload the functions required by `Show`, namely `show a`.

Now when we display the data type we get the format we expect.

And with that you've had your first introduction into Type Classes. `Date` is now an instance of the type class `Show`. If you want to see more information about the type class type `:info Show` into your GHCi instance.

You can see what functions exist in the type class, and instances that exist already.

A little more complicated example would be how to print out a Task. Some of the issues are:

- Incomplete and Completed Tasks look different

- Incomplete Tasks have optional Priority and Start Date

The first two `show` lines use pattern matching to distinguish between `Completed` and `Incomplete` tasks. Since our completed task contains an incomplete task, we are able to invoke show recursively rather than handling the same data twice.

To handle the optional fields we create a couple of helper functions that use pattern matching (there is often a lot of pattern matching in Haskell) to return the appropriate string.

Comparing with Eq and Ord Type Classes

Our next two type classes deal with comparing values. The `Eq` type class handles equivalency between two values.

From the info on `Eq` we can see that there are two functions defined: `(==)` and `(/=)`. The first take two instances of the same type and returns `True` if they are equal. The second function is for "not equals".

Let implement the equality check for `Date`.

The next type class for comparing is `Ord`, which is used for ordered data types.

The `Ord` type class is defined with a requirement that the type also be an instance of type class `Eq`. The class contains multiple functions but from the Hackage documentation of [Ord][3] we see that the minimal complete definition is to implement `compare`.

Our `compare` function is pretty simple to understand. Walking through the Year, Month and Day, if any of them are not equivalent then we use the built in `compare` method.

Filtering

Now that we can compare our data types, we can filter lists of data types.

The `filter` function has the type definition:

The first argument is a function that takes a value and returns a Bool, `True` if the value should be kept in the list. If we wanted to filter a list of `Tasks` and only return the `Incomplete` we could use `filter`.

`isPending` returns `True` for `Incomplete` and `False` for everything else.

A useful function is to check if one list is a subset of another. Using `filter` we can create such a function.

To break this down we will start inside out.

Check if the argument is an element of `ys`, and invert the result.

Running over `xs` each element is kept if it is not an element of `ys`.

Returns `True` if the list passed to `null` is empty.

If there are any values in `xs` that are not in `ys` they will be returned by `filter` and since the list passed to `null` would not be empty it returns `False`.

Using `subsetOf` we can filter for tasks that contain all the projects in a list.

Up next

If you want to see all the modifications for this post you can diff the code [here][4].

In the next post we will fix some of the bugs we introduced in this post by writing some Unit Tests.

Haskell Project: Stack and Data Types [1]

Prelude - Eq [2]

Prelude - Ord [3]

Code [4]

Haskell Project: Unit Testing with Hspec [5]

$ date: 2017-05-09 19:02 $

$ tags: haskell, tutorial $

-- CC-BY-4.0 jecxjo 2017-05-09

Comments?

back

Proxied content from gemini://gemini.sh0.xyz/log/2017-05-09-haskell-project-show-compare-and-filter.gmi

Gemini request details:

Original URL
gemini://gemini.sh0.xyz/log/2017-05-09-haskell-project-show-compare-and-filter.gmi
Status code
Success
Meta
text/gemini
Proxied by
kineto

Be advised that no attempt was made to verify the remote SSL certificate.