In this tutorial, you will learn how to create custom types in Elm. We would focus on algebraic data types and how it is implemented in Elm.
1. Algebraic Data Types/Union Types
An algebraic data type (ADT) is a type that is composed of other types. This allows us to define a type and specify all the instances the type can assume.
In the code below, we create a type called Greeting which can take either one of four variants:
type Employee = Doctor String String | Driver String String | Intern String Int | Janitor
The first three variants: Doctor, Driver and Intern are called data constructors. This is because they can be considered as a constructor with parameters. The last variant Janitor is a value by itself. It is also called a nullary data constructor, that is, a constructor that takes no arguments.
The Employee type we created in the previous section is called an algebraic data type or a union type or tagged unions.
2. Type Constructor
Maybe is a built-in type in Elm that allows you to model the idea of possible non-existent value. Sometimes, we are not sure whether a value is returned. For instance when converting a String to Int. The convert could either succeed and return a valid value or It could also fail if the input string is could not be converted.
The type definition for Maybe is
type Maybe a = Just a | Nothing
Another example would be attempting to 11th element of an 10 element array. In this case, instead of returning an error, we return Nothing.
To create a value of type Maybe, we could either use the Just data constructor or the Nothing constant. Notice that in the type definition for Maybe, we have a value a. This is a type variable that represents a an argument to a type constructor.
We can also create our own generic type simply by passing an argument to the type constructor. In the code below, we modify the Employee type so that it accepts a type argument.
type Employee a = Doctor String String | Driver String String | Intern a | Janitor
So now that we can pass an argument to Employee, the Intern data constructor can take any type.
3. Type Annotation ( filterMap Function)
Functions and values in Elm can be annotated with type annotations. They tell us how the function work. For example, if you type List.filterMap in the Elm Repl, you can see the type annotation as shown below:
> List.filterMap <function> : (a -> Maybe b) -> List a -> List b
The type annotation tells us that the filterMap function takes two arguments:
- a function that takes a value and returns a Maybe b
- a list of values of type a
Finally, the filterMap function returns a list of values of type b. The filterMap applies the function to each element of List a keeps the result when a value is returned.
You can try it with the example below:
List.filterMap (\x -> toInt x) ["2", "3s", "4"]
This would output the result [2, 4]
4. Recursive Types
One typical recursive type in Elm is a list. This means that a list of values is build recursively with an empty list as the base. So if we have the list [2, 13, 9], it is actually constructed in the following steps:
- []
- 9 :: [] = [9]
- 13 :: [9] = [13, 9]
- 2 :: [13, 9] = [2, 13, 9]
Let’s create a recursive data structure
type MyType a = Empty | Node a (MyType a)
The above definition means that a MyType can either be Empty or Node followed by another (MyType a). A list with no element is represented as Empty while a MyType with one element is represented as Node a Empty.