You may have noticed that I have been talking a lot about Haskell lately. In this post, I’d like to write a beginner-friendly summary in layman’s terms with regards to why I have taken such an interest in a language I’ve only started using. Hopefully this will inspire some people to consider using alternate languages to the ones they’re used to.
What is Haskell?
Let’s start with what functional programming actually is with a few examples. In essence, functional programming is a mind shift from Imperative programming (the one most people are used to), where additional emphasis is placed on the manipulation and transformation of data through functions. Someone who’s more of an expert in Haskell or FP in general could give a better answer, but I’m rolling with this explanation for people like my past self. It can be hard to imagine the differences between the two when you haven’t really used any functional languages, so just try to keep up and hopefully I can make this relatively painless.
To explain what I mentioned above, I want to start talking about what we mean when we say ‘function’. In mathematics, you can imagine the ‘number machine’ teaching methods they use early on. You place some numbers or some form of data inside the function, and it spits out different numbers or data. However, when you start learning programming you quickly forget this idea and instead imagine functions as a set of instructions — you are telling the computer to do something in an order in which operations are executed. There are loops, there are if statements, but at the end of the day there is some explicit instruction set that your program is following. The entirety of a game is within a loop that keeps going until the game closes — you pass in movement and every frame the character moves, the world reacts and finally the world is drawn.
When we talk about imperative vs functional programming, forget concepts of Object Oriented and Procedural programming etc. We aren’t talking about the ways of programming in a language, we’re talking about the type of programming the language itself is capable of doing. They are independent and so you shouldn’t dwell on them for this post. Just wanted to clear this up in case you then start questioning the differences between procedural and imperative when you usually do procedural programming inside an imperative language.
Back on topic. Functional programming is an entirely different ball game. It’s very difficult to translate imperative code into functional because of the way things are done. There’s a wealth of information and discussion on the topic so I won’t go into too much detail, but rather try to navigate the simplest aspects so that people who haven’t given it a go can still understand it. Below is an example of imperative programming:
So in this example, we have a very simple class
Player which has a single function
MovePlayer. This function returns absolutely nothing, it is a set of instructions detailing how the player will respond to some form of input. When we call the function like so:
MovePlayer(1, 2); when the program has just started, we should expect the coordinates of the player to then be
(1, 2) assuming that the rest of the function is completed. Calling it twice will move the player to
(2, 4) and so forth. This is a simple example of Object Oriented Programming and is very effective whilst also being easy to read.
When looking at
MovePlayer, you start at the top and read it downwards — why wouldn’t you? This feels natural and it actually looks like a list of instructions — you can role-play as the computer and do everything in your head step by step. When navigating
if statements it’s easy to see the flow of the function and which line is executed next.
Time to bring in some FP. The fact that this function returns nothing (well,
Void, as you can actually return
Nothing in Haskell!) shows that this function ‘does’ something, it doesn’t transform your data in any way, it just stores it away and accumulates it. The structure of this function doesn’t exactly translate cleanly into a functional language at all. Only a few functions in Haskell ‘do’ things — think of it in a sense that one function will be impure that will use all the pure functions to get things done.
With imperative programming, states can be found everywhere. There is a sense of ‘state’ for every instance of the player class as they each hold data that details where they are at any moment in time. In Haskell, without getting into advanced techniques, there’s not really a sense of state anymore. We will go into what purity means in the next chapter, so we will ignore the sense of state (the
Player class) and instead look at a function that
Now for those who are unfamiliar with Haskell may feel a little scared over the code above. This can definitely be cleaned up to be more elegant, but I feel this version demonstrates quite a few features of Haskell. Firstly, we have defined almost a ‘mini-function’ called
calc. We are no longer reading the function from top to bottom as in order to understand the second line we need to have read
calc first. Haskell is very mathematics based, and so you need to imagine that you are writing a mathematical function rather than a set of instructions. In this case, we defined a function which investigates two vectors and adds their components together and ensure that they are within 0 and 1000. Obviously, if the bounds were to be any different, we either need to hard code them in (by replacing
0) or by passing them in.
Secondly, let’s address what the first line actually means. The first line is the type declaration of the function — in this case, we are passing in two tuples (pairs in this case) containing two
Ints each, and then finally returning a tuple. As you can expect, this function takes a vector to represent a position and a vector in which to translate by. The parameters are split up by
-> where the last section is the return value. By following the rules we established in the previous example, this function should hopefully spit out another tuple of
Ints where the position will eventually be. The reason I placed emphasis on the fact it is a position is because this function does not belong to any class or instance; anything can call it. Because of the fact that we pass in the position we are manipulating and also returning the result, we are able to call this a pure function that is completely free from side effects and does not depend on anything else to be called. If you are into Parallel Computing, this is a very good thing as it means that two functions cannot touch the same data at the same time.
Following from the previous point — the fact that you are always operating and returning data means that another strength of Haskell and other functional languages is that data is immutable; once data is created it cannot be changed. If you use data properly and wisely, this can lead to saving memory. It is also more secure — unless you overwrite your variable (which is usually done in
do syntax, a bit out of scope of this blog), your data will always be the same. You don’t usually overwrite data a lot in Haskell, but if you did it would be a lot like overwriting a variable passed by value where you are only messing with the copy.
Let’s look into
Pure functions closely. As described earlier, a pure function has no side effects. By side effect, we mean something which is done as a consequence of calling a function. This could be changing values that aren’t a part of the function or simply using them. Here’s an imperative example:
That’s a very simplistic function written in C#. It doesn’t take much to realise that there isn’t much of a point to this function when you can use
+ to make it completely redundant. Let’s mix it up a bit.
So what’s different between these two functions? Well, again, it doesn’t require a high IQ to see that there’s now an external variable named
c which is going to be added to whatever we pass to our function. What’s important to note here, is that passing in
a = 1 and
b = 2 may not always return
8 with this function so long as
c is visible outside the function. This is where the word
pure comes from: a pure function will always return the same outputs for the same set of given inputs. When doing functional programming, the majority of your functions will follow this pattern. You should be rubbing your head right now if you’re a more involved programmer — how the hell do you ever get anything done?
The answer is to divide your program into pure and impure parts. You begin to look at what computations actually require a sense of state and filter down your functionality into small, focused functions that only focus on manipulating data. When you wrap things up, you will likely have a few or even just one impure function that ties everything together. The key to FP is to really evaluate the purity of every function to ensure that you can have as many pure functions as possible.
Now it’s time to look into the other buzzwords that are tagged in Haskell’s description. Lazy Evaluation means that you can actually utilise lists of infinite length without crashing the program. Haskell will only evaluate expressions when it needs to, so taking the 32132nd number from an infinite list only requires the list to be calculated to that point. Here’s a few examples of some infinite lists written in different ways:
I guess you could say I was pretty lazy in this area as I just demonstrated infinite lists rather than really talking about them, but who cares?
This subject isn’t specific to Haskell, but I’ll give you the run down anyway as you may not be clear on it in the first place. Essentially, in Static Typing the type of every variable is known at compile time. Usually, this means that the programmer must explicitly say what type a variable is before you can use it. This is obvious in C++:
In Haskell, there’s something called ‘type inference’ which means that you don’t necessarily have to define all of your variable types because the language is very smart. You should still explicitly label your functions though to make sure the compiler is on the same stage as yourself. C++ and Haskell are both statically typed languages, including (but not exclusive to) Java, C and Scala.
Int, for instance. Remember that some statically typed languages are capable of this, but that’s just the compiler being clever and doing the work for you. People say JS is ‘untyped’, but I’m just going to call it dynamic typing as it is most definitely not static. Now, at the risk of confusing you, I’m going to talk about dynamic typing with regards to Strong and Weak typing. These are two different concepts and so you shouldn’t get mixed up between them — just because a language is dynamically typed doesn’t necessarily mean it’s strong (or weak) typed.
If you are working with a strongly typed language, then once you have bound a variable to a type then you must ensure that it is always that type. For instance, does concatenating (joining) a
String with an
Int work? Does
"High " + 5 compile somehow? If it does, is there an explicit method for it, or is it a feature of your language that allows types to mingle like this? A weakly typed language will easily allow it, resulting in
"High 5". This is a really simple example and isn’t so important, but it’s hard to talk about dynamic typing without mentioning it.
No, it’s not the spell in World of Warcraft, however, the meaning behind the two go together. Essentially, Polymorphism allows a variable to have more than one type. Once the type is bound then it must remain consistent through every call to said variable or parameter, but until that point it can be anything you like within reason. Haskell has multiple ways to do this, the first is very simple to demonstrate. If you have ever heard of an identity function, then this will quickly make sense:
Note that in Haskell, we read
:: as ‘has type of’ as in ‘The function
id has a type of
a -> a’. This isn’t really saying what the function actually does, however, given the type, there’s not a lot it could do. So what is
a? Well, we don’t know — it’s polymorphic. Since we have no idea what it actually is or what it could be, we can’t really pass it to any functions unless the function also takes a parameter of
a, meaning that it accepts anything.
a -> a means that this function will take a variable of any type and will return a variable of the same type. If you aren’t sure what an identity function is, it is simply a function that, in context, does nothing to it’s arguments.
1 is the identity for multiplication and division because anything
/1 equals itself —
x * 1 = x. In addition (and subtraction),
0 is the identity because
x + 0 = x. In this case,
id is an identity function which does nothing to it’s parameters and simply returns them. It might not make sense now, but it does have it’s uses.
id is a polymorphic function because it accepts literally any type. It doesn’t need any information on what the thing it accepts is — it could be a list or even another function — that’s for another blog post ;). The function will always return something of the same type and will be bound as soon as you pass in a parameter. Here’s an example:
As we went through before, the
-> symbol separates the parameters from each other, where the last one on the line is the return value. The type of
id binds itself to whatever variable type we pass in. It is also important to remember that
a is just easy to use as it’s the first letter of the alphabet, but the name doesn’t matter. Types start with an uppercase letter, such as
String. Anything lowercase can be seen as a variable of any type.
a -> b means we have a function returning something that could be a different type to the one we passed in.
a -> b -> c means that we pass in two parameters that aren’t necessarily the same type, and get back another type that could be different, or the same as
b. Remember, these letters could be anything. Just because
b indicate that there are uses of two independent types in our function doesn’t mean that they have to be different, just that they could be.
So what was I going on about, surely that first call was
Int -> Int, I nailed that Ashley! I had it guessed! Well, in this case, you are actually a bit too specific. It’s time to introduce
=> in the context of type declarations.
So when we use Haskell in a terminal to evaluate expressions we can use
:t to query the type of something. This is querying the type of the result of
id 1, so don’t get confused. Our answer was
Num a => a, which is a little different to what I’ve shown so far.
=> describes the thing on the right with the definitions on the left — it’s saying that the
a after the arrow is deriving the typeclass
Num. As I was saying, we asked for the result of the function, and so our compiler is telling us that the result is
a is part of the Num typeclass. Therefore, the specialised type of our function
id before the function was applied became as follows:
You can still see the original
a -> a type declaration coming through, but now we know that the
a sent in and
a retrieved, which are both the same type, are now a
Num. What is
Num? The second way Haskell is polymorphic is less generic than the previous. As you get more specific, you get more functionality at a cost of being general — it makes sense really, as you can’t attach functionality to something without defining some basic principles to manipulate.
Num, a typeclass for a number. When something derives
Num, we mean that whatever the type is it has to have some core functions. Put it this way, when you derive
Num, you have to then fill in some functions for your type showing how they operate — if you make some whacky type that derives
Num, you need to work out how things like
* work (you can research why you don’t define
/). The functions can be found here on Hackage — just make sure you let the page load and scroll to the
Num section or you’ll get confused. Let’s have a look at the functions from that page:
As you can see from that last demonstration,
Int actually has a lot of functionality! If we have a function that has a declaration such as
Eq a => a -> a -> Bool, we can start thinking about the types of things it could do.
Eq is the typeclass of equality — in terms of checking if things are equal. If we have a function which takes two variables of the same type that have the ability to be compared to see if they are equal or not, we can see how we would get a
Bool out of this (where
Bool is either
False). This function would still be polymorphic as we don’t know what type we are being given, all we know is that they can be compared. It isn’t 100% generic, but neither is it 100% specific, therefore qualifying as polymorphic.
You may already know how your current language implements polymorphism, whether it is through inheritance of other classes or some other method. This is simply how Haskell does it, and it is very, very powerful.
Wrapping things up
I hope this little adventure has been a fun read and that I actually convince some of you to dive into Haskell a little bit. Learning Haskell has been a huge help to my challenge-craving brain and I am in love with how mathematical everything is. If you are interested to start learning, take a look at Learn you a Haskell for a Great Good (LYAH) which is free, or the Haskellbook which is more thorough, more complex and more expensive. If you want to get serious, go for the Haskellbook, but I would advise that you have a look at LYAH first for a quick overview of what you can do. It’ll get you up and running to the point of playing around with code, but the more academic of you will probably need something more.
If you want something else too look at, go and take a look at ComputerPhile on YouTube, they’ve done some great videos on not just Haskell and not just on Functional Programming, but on computing in general. Definitely check out the Haskell videos though. If you need some help getting started (or even when you’re an expert!) or want to talk to some Haskell pros, go to the #Haskell IRC on Freenode or the Functional Programming Discord Server.
Lastly, give me a follow and a tweet if this has helped. I am doing my best to share what I know to like-minded people who hunger for academia.