Haskell/More on datatypes

Enumerations
One special case of the  declaration is the enumeration — a data type where none of the constructor functions have any arguments:

You can mix constructors that do and do not have arguments, but then the result is not called an enumeration. The following example is not an enumeration because the last constructor takes three arguments:

As you will see further on when we discuss classes and derivation, there are practical reasons to distinguish between what is and isn't an enumeration.

Incidentally, the  datatype is an enumeration:

Named Fields (Record Syntax)
Consider a datatype whose purpose is to hold configuration settings. Usually, when you extract members from this type, you really only care about one or two of the many settings. Moreover, if many of the settings have the same type, you might often find yourself wondering "wait, was this the fourth or fifth element?" One way to clarify is to write accessor functions. Consider the following made-up configuration type for a terminal program:

You could then write accessor functions, such as:

You could also write update functions to update a single element. Of course, if you add or remove an element in the configuration later, all of these functions now have to take a different number of arguments. This is quite annoying and is an easy place for bugs to slip in. Thankfully, there's a solution: we simply give names to the fields in the datatype declaration, as follows:

This will automatically generate the following accessor functions for us:

This also gives us a convenient update method. Here is a short example for a "post working directory" and "change directory" functions that work on s:

So, in general, to update the field  in a value   to, you write. You can change more than one; each should be separated by commas, for instance,.

It's only sugar
You can, of course, continue to pattern match against s as you did before. The named fields are simply syntactic sugar; you can still write something like:

But there is no need to do this.

Finally, you can pattern match against named fields as in:

This matches the variable  against the   field in the   and the variable   against the field. These matches will succeed, of course. You could also constrain the matches by putting values instead of variable names in these positions, as you would for standard datatypes.

If you are using GHC, then, with the language extension, it is also possible to use this form:

It can be mixed with the normal form like this:

(To use this language extension, enter  in the interpreter, or use the   pragma at the beginning of a source file, or pass the   command-line flag to the compiler.)

You can create values of  in the old way as shown in the first definition below, or in the named field's type, as shown in the second definition:

The first way is much shorter, but the second is much clearer.

WARNING: The second style will allow you to write code that omits fields but will still compile, such as:

Trying to evaluate the unspecified fields will then result in a runtime error!

Parameterized Types
Parameterized types are similar to "generic" or "template" types in other languages. A parameterized type takes one or more type parameters. For example, the Standard Prelude type  is defined as follows:

This says that the type  takes a type parameter. You can use this to declare, for example:

The  function takes a list of birthday records and a string and returns a. The usual interpretation of such a type is that if the name given through the string is found in the list of anniversaries the result will be  the corresponding record; otherwise, it will be. is the simplest and most common way of indicating failure in Haskell. It is also sometimes seen in the types of function arguments, as a way to make them optional (the intent being that passing  amounts to omitting the argument).

You can parameterize  and   declarations in exactly the same way. Furthermore you can combine parameterized types in arbitrary ways to construct new types.

More than one type parameter
We can also have more than one type parameter. An example of this is the  type:

For example:

In this example  indicates how many groups you would have if you paired off a certain number of people for your activity. It can also let you know if you have too many people for your activity or if somebody will be left out. So  will return either an Int representing the number of groups you will have, or a String describing the reason why you can't create your groups.

Kind Errors
The flexibility of Haskell parameterized types can lead to errors in type declarations that are somewhat like type errors, except that they occur in the type declarations rather than in the program proper. Errors in these "types of types" are known as "kind" errors. You don't program with kinds: the compiler infers them for itself. But if you get parameterized types wrong then the compiler will report a kind error.