Introduction

Welcome to part 1 of the Monday Morning Haskell Liftoff Series! If you’ve always wanted to try learning Haskell but were never able to find a good tutorial for it, you’re in the right place! In these three articles, we’re going to take you from zero knowledge about Haskell to understanding enough of the basic concepts so that you can start programming on your own.

This article will cover a few different topics. First we’ll download all the Haskell tools we’ll need. Then we’ll start writing our first expressions and learning a bit about Haskell’s type system as well. Next, we’ll put the “function” in functional programming and learn how Haskell’s functions are first class citizens. Finally, we’ll wrap up by talking about some slightly more complicated types like lists and tuples.

If you’ve already read this article or are familiar with all these concepts, you should jump straight to part 2, where we talk about writing our own code files, and writing more complicated functions with some advanced syntax. Make sure you also don’t miss part 3, where we’ll look at how easy it is to create our own data types in Haskell!

Finally, once you’re done with these, make sure to check out our Stack Video Tutorial! This lesson will teach you the basics of using the Haskell Stack tool to make your own projects, so you can start using other awesome libraries people have made! So without further ado, let’s get cracking at installing Haskell!

Installing and Running the Interpreter

If you have not touched any Haskell whatsoever, the first step is to download the Haskell Platform. Download the latest version for your operating system from haskell.org/platform and follow the on-screen prompts.

The Haskell Platform currently includes 4 main items. First, it has GHC, the most commonly used Haskell compiler. The compiler is what takes your Haskell code and turns it into something the computer can run. Second, it has GHCI, and interpreter for the Haskell language that allows you to enter expressions and test some calculations without having to put your code in a separate file.

Third, it includes “Cabal”, a dependency manager for Haskell libraries. This will allow you to download Haskell code that other people have written and use it in your own projects. Finally, there is the “Stack” tool. This adds another layer on top of Cabal and makes it easier for you to download packages that won’t cause conflicts. If you want more details about Stack, make sure to check out the video tutorial!

You’ll know you’ve successfully installed all these elements when you can open up a terminal or command line prompt and use the command “ghci” to bring up the interpreter. We’ll spend the rest of this lesson in the interpreter trying out some of Haskell’s basic language features.

Expressions

So the most fundamental thing about Haskell (and most functional programming languages for that matter) is that everything in the language is an expression. All your programs actually consist of is evaluating these expressions. Let’s start by examining some of the most basic expressions we can make. Try entering the following 6 expressions in the interpreter. When you press enter each time, the interpreter should simply echo back what you typed.

>> True
True
>> False
False
>> 5
5
>> 5.5
5.5
>> ‘a’
‘a’
>> “Hello”
“Hello

With this series of expressions, we cover most of the basic “types” of the language. If you’ve done any programming in the past, these basic types should be pretty familiar to you. The first two are “boolean” expressions. True and False are actually the only values of this type. We can also represent numbers as expressions, both whole and decimal. Finally, we can make expressions representing individual characters as well as whole words, which we call strings.

In the interpreter, we can assign expressions to names by using “let” and an equals sign. This saves the expression under that name so we can refer to it later.

>> let firstString = “Hello”
>> firstString
“Hello”

Types

Now, one of the cool things about Haskell is that every expression has a type. Let’s examine the types of the basic expressions we entered above, and we’ll see that the ideas we were talking about are actually formalized in the language itself. You can examine the type of any expression by using the :t command:

>> :t True
True :: Bool
>> :t False
False :: Bool
>> :t 5
5 :: Num t => t
>> :t 5.5
5.5 :: Fractional t => t
>> :t ‘a’
'a' :: Char
>> :t “Hello”
"Hello" :: [Char]

A couple of these are straightforward, but a couple a kinda weird. Isn’t that last one just a “String”? Well yes. You can use the term “String” in your code. Under the hood though, Haskell thinks of Strings as a list of characters, which is what [Char] means. We’ll get to that later. True and False correspond to the “Bool” type, just like we’d expect. The ‘a’ character is a single Char. Our numbers are a little more complicated. Ignore the “Num” and “Fractional” words for now. This is how Haskell refers to a whole range of types. We’ll think of whole numbers as having type Int, and floating point numbers as having type Double. We can explicitly assign the type we like like so:

>> let a = 5 :: Int
>> :t a
a :: Int
>> let b = 5.5 :: Double
>> :t b
b :: Double

One thing that is important to note is that Haskell can infer some information about the types of our expressions just from their form. We generally don’t need to explicitly give a type to each of our expressions like we do in a language like Java or C++. Let’s start doing some computations with our new expressions and see what we can come up with.

Functions

We can start with some basic mathematical calculations:

>> 4 + 5
9
>> 10 - 6
4
>> 3 * 5
15
>> 3.3 * 4
13.2
>> (3.3 :: Double) * (4 :: Int)

Once again, we’ve been thrown a curveball by this last example! By the time we’re done with this section, we’ll understand what’s going on here and how we can fix it. Now, an important note here is that we said everything in Haskell is an expression, and every expression has a type. So logically, we should be able to ask and determine the types of these different expressions. And we certainly can! We just have to put parentheses around them to make sure the type command knows to include the whole expression.

>> let a = 4 :: Int
>> let b = 5 :: Int
>> a + b
9
>> :t (a + b)
(a + b) :: Int

This makes perfect sense, since the result, 9, seems like an Int as well. But this is where it gets cool. The + operator, even on its own without the numbers, is still an expression! It is our first example of a function, or an expression that takes arguments. When we represent it by itself, we put parentheses around it.

>> :t (+)
(+) :: Num a => a -> a -> a

This is our first example of representing the type of a function. The important part is a -> a -> a. This tells us that (+) is a function taking two arguments, which must have the same type. It will then give us a result that has the same type as the inputs. The Num a portion specifies we have to use numeric types, like ints and floats. We can’t for instance do:

>> “Hello “ + “World”

But this explains why we were not able to add an int and a double together. The function demands we use the same type for both arguments. To fix this, we’d have to use a different function to change the type of one of them to match the other. Or we could let type inference figure it out, like we had in the example right above it. But we’re getting ahead of ourselves. Let’s focus on the semantics of how we “apply” this function.

In general, we “apply” functions by placing the arguments after the functions. The + function is special in that we can use it as an operator in between its arguments. If we want though, we can use parentheses around it and treat it like a normal function. In this case, both arguments need to come after it:

>> (+) 4 5
9

What’s really cool about functions is that we don’t have to apply all the arguments at once!. We can take the same addition operator and apply it to only one of the numbers. This is called partial application.

>> let a = 4 :: Int
>> :t (a +)
(a +) :: Int -> Int

On its own, (+) was an operator that could take two arguments. Now we’ve applied one argument (an Int) to it. The resulting expression is now a function that takes one remaining argument. Furthermore, since the first argument we passed was an Int, the second one must also be an Int. We can assign our partial function to an expression using “let” and then apply it to a second argument!

>> let f = (4 +)
>> f 5
9

Let’s experiment a bit with some more operators, this time on boolean values. These are important because they’ll let you build more advanced conditionals when you start writing functions. There are three main operators, which work the way you would expect for other languages: And, Or, and Not. The first two take two boolean parameters and return a boolean, and the last takes a single boolean and returns it.

>> :t (&&)
(&&) :: Bool -> Bool -> Bool
>> :t (||)
(||) :: Bool -> Bool -> Bool
>> :t not
not :: Bool -> Bool

And we can see some simple examples of their behavior:

>> True && False
False
>> True && True
True
>> False || True
True
>> not True
False

One final function we’ll want to know about is the equality function. This can take two arguments of almost any type and determine if they are equal.

>> 5 == 5
True
>> 4.3 == 4.8
False
>> True == False
False
>> “Hello” == “Hello”
True

Lists

Now we’re going to broaden our horizons a bit more and discussed some more advanced types. The first concept we’ll look at is lists. These are a series of values that have the same type. We denote lists by using square brackets. A list can have zero elements, and we call this the empty list.

>> :t [1,2,3,4,7]
[1,2,3,4,7] :: Num t -> [t]
>> :t [True, False, True]
[True, False, True] :: [Bool]
>> :t [“Hello”, True]
Error! (these aren’t the same type!)
>> :t []
[] :: [t]

Notice the error in the third example! Lists can’t have different types of elements! Remember we said earlier that a string is really just a list of characters. We can test how this looks:

>> “Hello” == [‘H’, ‘e’, ‘l’, ‘l’, ‘o’]
True

Lists can be combined using the (++) operator. Because strings are lists, this allows you to combine strings like you can in other languages.

>> [1,2,3] ++ [4,5,6]
[1,2,3,4,5,6]
>> “Hello “ ++ “World”
“Hello World”

Lists also have two functions that are specially designed to get certain elements out. We can use the head function to get the first element of the list. Similarly, we can use the tail function to get all the elements of a list EXCEPT the head element.

>> head [1,2,3]
1
>> tail [True, False, False]
[False, False]
>> tail [3]
[]

Beware though! Calling either of these functions on an empty list will result in an error!

>> head []
Error!
>> tail []
Error!

Tuples

So now that we know about lists, you might be wondering if there’s any way we can combine elements that do not have the same type. In fact there is! These are called tuples! You can make tuples that have any number of elements, each with different types. Tuples are denoted using parentheses:

>> :t (1 :: Int,”Hello”, True)
(1 :: Int, "Hello", True) :: (Int, [Char], Bool)
>> :t (1 :: Int, 2 :: Int)
(1 :: Int, 2 :: Int) :: (Int, Int)

Each tuple we make has its own type based on the constituent types within the tuple. This means the following are all different types, even though some of them share the same types of elements, or have the same length:

>> :t (1 :: Int, 2 :: Int)
(1 :: Int,2 :: Int) :: (Int, Int)
>> :t (2 :: Int,3 :: Int,4 :: Int)
(2 :: Int,3 :: Int,4 :: Int) :: (Int, Int, Int)
>> :t (“Hi”, “Bye”, “Good”)
([Char], [Char], [Char])

Since tuples are expressions like any other, we can make lists out of them! However, we cannot combine tuples of different types in the same list.

>> :t [(1 :: Int,2 :: Int), (3 :: Int,4 :: Int)]
[(1 :: Int, 2 :: Int), (3 :: Int, 4 :: Int)] :: [(Int, Int)]
>> :t [(True, False, True), (False, False, False)]
[(Bool, Bool, Bool)]
>> :t [(1,2), (1,2,3)]
Error

Conclusion

This concludes the first section of our liftoff series. Look at how much we’ve gone through in one article! We installed the Haskell platform and started experimenting with the Haskell interpreter. We also learned about expressions, types, and functions, which are the building blocks of functional programming in Haskell.

In part 2 of this series, we’ll start writing our own code in Haskell source files. We’ll examine how we can print output to a user of our program, as well as get their input. We’ll also start writing our own functions and look at the various syntax elements Haskell gives us to specify function behavior.

Then in part 3, we’ll start building our own data types. We’ll see how simple Haskell’s algebraic data types are, and how type synonyms and newtypes can give us additional control over the style of our code.

And if you’re itching to do some more advanced Haskell work, be sure to check out of Stack Mini Course! This will walk you through creating a basic Haskell program with the Stack utility! You’ll be able to seamlessly incorporate code libraries from online and build your own applications piece by piece!