This is part 1 of a 6 part tutorial
Data Structures as Containers
In a language like C, you can think of data structures as containers which hold one or more objects.
A Container
1 2 3 |
|
The int_container_t
type is now something which holds one int. To make the same sort of data structure in Haskell you would
write:
Int container in Haskell
1
|
|
Ignoring for a moment the suffix deriving Show
, this looks a lot like a function declaration, because that is essentially
what it is. The identifier IntContainer
appears on both sides, but the two are actually in two different namespaces. It is
not necessary for these two identifiers to have the same name, but people will often do this by convention.
The left hand side of this declaration names a new type IntContainer
, and the right hand side is defining a data constructor
for this type, essentially a function named IntContainer
which takes one Int
as argument which it uses to create an instance of this type.
Using the data constructor in ghci
1 2 3 4 5 6 7 8 9 10 |
|
If I pass the data constructor a 4
, it returns something which is of type IntContainer. The ghci command :t
can be used
to get type information about just about anything in Haskell. IntContainer :: Int -> IntContainer
is read “IntContainer is
a function which accepts an Int and returns an IntContainer”. i :: IntContainer
is read “i is of type IntContainer”.
Algebraic Type System
In C++, we have a template system which allows us to create classes and methods where the types involved are variable. If we wanted to create a Container class in C++ which could hold not only an int but any type, we could write a class like this:
C++ Templated Container
1 2 3 4 5 6 7 |
|
Haskell has an algebraic type system which enables us to do what C++ does with its template system, but is far easier to use and debug. Much of the simplicity comes from the lack of pointers in Haskell, and the fact that compound types and functions are treated the same as primitives without needing to put much thought into copy constructors and memory management.
Haskell container which can contain any type
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
This time, the left hand side of the type declaration is parameterized with a variable type t
, whereas
in IntContainer
it did not. A type variable like t
is a placeholder for a type, so on the right hand
side t
can be replaced by any type, for example when you pass the data constructor a String
, it is as
if you had declared data Container = Container String deriving Show
. In all, so far this is very much
like C++ templates.
In the above example it is shown this works with Int
, Float
, String
, or even [Int]
(a list of Ints).
It will even accept another function, as shown as I pass it the lambda (\x -> x + 1)
, because a function
is a typed value just like anything else. This creates a value with the type signature x :: Container (Integer -> Integer)
meaning “x is a container holding a function which accepts an Integer and returns an Integer”.
Everything works fine until I type x
and ghci’s read eval print loop tries to print a the function (x -> x + 1)
.
The other data types like Int
and String
have properly defined Show
functions already, but a lambda
does not have any meaningful way to display itself. I have told Container to derive its show function
where it is possible, but it was not possible when passed a lambda function, therefore we got an error.
Typeclasses in Haskell
A class in Haskell is not quite the same as in object oriented programming languages. To define a class you essentially define only an interface, and it is not necessary to provide any implementation. Any type can be a member of a class if it declares that it supports the class’s interface.
Let’s define a funny example class:
Defining a class
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Now Container is an instance of Show and PikachuShow.
Container
derives its show
function from its data members such as String
or Integer
which are
instances of Show
. (Integer -> Integer)
is not an instance of Show
and container cannot find a show
implementation for it. Container
is an instance of PikachuShow
and has therefore implemented the pikaShow function
The pikaShow
function ignores its argument, because of the _ in the function argument list, this is also
seen in languages such as Erlang, and to some extent in Ruby. The argument, if you had bound it to a variable,
would be the Container
it was called with.