Functional Reactive Programing with Elm: Part VI - Types
In this section we will look at the role played by Types in Elm.
Statically typed systems: Languages like C, Java and C# are examples of languages that fall in this category. They are characterized by the fact that the types of all variables have to be declared/known at compile time when the type checking is done.
Dynamically typed systems: Languages like Python and Ruby are examples of languages that fall in this category. They are characterized by the fact that the types of all variables change depending on what is assigned to them. In other words types are associated with values not variables. These languages typically do most of their type checking at run time.
Type Inference: is when the compiler can infer the types of variables and automatically assign them to variables. In case the user has declared the type this information is used to verify the assignment. Type checking like static languages is done at compile time. This approach tries to keep the benefits of static typing while allowing the programmer to not “have to” declare the types all the time.
The subject of type systems is vast and we cannot do justice to the subject here so I have only highlighted a couple of interesting features. You can begin a long journey of understanding and analyzing Type Systems here.
It is tempting to think that we have the best situation when we languages that support static typing where possible and dynamic typing where needed.
Elm as we shall see is a statically typed language with support for type inference.
Here we can see a list of
Basic Types
Let us create an instance of each of the Basic types:
Type aliases are not a mechanism by which you can give more relevant type names to built in types. Here are a couple of examples:
Records
Even though functional programming is all about programming with functions, we still need to work/operate on data.
This is where records come in.
For example let us consider we want to work with data for an Employee which we take to contain an id : Int , firstName : String, lastName : String. We can use a record to represent this data. While it takes a little time designing good records to represent your data will make your programs easier to understand and maintain.
Records are a lightweight data structure similar to a tuple in that it can hold a fixed number of different types. Unlike tuples the elements of a record have names and accessors that allow us to work with the data stored in a record. You can work without type aliases for a record, but if you are going to create multiple instances of the record, then it is more compact and less error prone to create the type alias.
Often we need to describe fixed sets of values in our programs.
For example we can describe the state of a program as {NotStarted,Running,Completed}. Of course we could represent this as a set of integers, but Enumerations are a much nicer way to do this.
We then use the Enumerations typically as switches to control the logic we use to handle these different cases.
In this case we have just implement simple function that converts the different Status cases to strings. But you could write similar functions to trigger different functions based on the Status enumeration.
Algebraic Data Types
An algebraic data type (ADT) allows us to create a composite type that consists of other types. (A record is also a composite type which requires all the contained sub types that go into its definition to be present as specified.)
Union Types - OR Types:
Let us look at an example of an ADT (Union Type) arising in a in a situation where we are modeling two types of geometric objects: Circles and Squares. Then we could write a program to manipulate them as follows:
Notice how using pattern matching we are able to route the flow of the program to the appropriate section of code based on the
type.
Generics or Parametrized Types
Now we come to an extremely powerful concept; can we write
code that will work for all types. This is especially useful when we are designing data structures that store different types. We see in this section that types can take parameters that are other types. And this will give us great flexibility and we can write more “generic” code.
Creating an ADT:
So let us look at a simple data structure, suppose we have a situation where we we want to have a type that can have either an Integer or be empty.
Here is a simple implementation using ADTs:
So that is great, we are about to celebrate when we realize that we need to have OptionalFloat as well!
Creating a Parametrized ADT :
The best way to create containers that can store multiple types is to use a parametrized ADT. Here is an example where we modify the OptionalInt type to a parametrized ADT so that it can store any type!
As the type declaration shows
type OptionalValue a = Nothing |OptionalValue a
OptionalValue is a parametrized type that takes a type parameter a as part of its definition.
Then when we create an instances of OptionalValue like:
we specify the type parameter that we want to use.
You will find that using parametrized types an extremely simple, expressive and powerful programming technique.
In Elm there is a built in type called Maybe that is the same as the Optional type that we just created for illustration purposes.
Recursive Types
And now we come to the question can a type refer to itself in its definition? You might ask why do I want to do that?
Here is a simple example of a binary tree from the elm documentation:
Type Classes - number, appendable, comparable etc.
I will not get into a discussion on Typeclasses as the creators of Elm wanted to suppress the complexity of the more advanced usage of types. (You will have to refer to my Haskell notes to see a description of typeclasses.)
Anyway, number (is essentially a built-in type class in Elm, there are a few more like this (e.g appendable)) and represents a type that supports addition, subtraction, etc. I did not manage to find a lot of documentation on typeclasses in Elm so these comments are based on my experimentation and may be inaccurate.
So what is see above is a simple use of the number type. The function triple is declare to have type triple : number -> number. So that means this function will accept any type that supports operations like +, - etc. (or more precisely is an instance of the number typeclass.)
Okay, that is about all the detail we will go into here on typeclasses but at least you get a sense of how to use it, and a flavor of what it means.
Collections:
Elm has built in support for some essential collection data types:
Lists : is a collection of values of the same type, that can have different/changing length.
Tuples : are kind of “dual” to lists in that they are collections that have fixed length, but can has varying types.
Dictionaries is an extensible collection that stores pairs of keys and values.
Arrays
A simple example of the usage of the Elm dictionary: