I wrote Python for the last 10 years, and I always tend to write code in a "functional" way - map, filter, lambda and so on, it makes me feel clean and happy. Following this warm feeling I decided to try a True functional language.

So I read a couple of tutorials online and went through the famous Learn You a Haskell for Great Good ! It was a long and painful journey - which is not over yet. After some retrospection I wanted to share my own introduction to Haskell based on my python experience. What I wished I had learnt first and what I wished I learnt later down the road.

What is Haskell

According to Wikipedia:

Haskell (/ˈhæskəl/[27]) is a general-purpose, statically typed, purely functional programming language with type inference and lazy evaluation.[28][29] Designed for teaching, research and industrial application, Haskell has pioneered a number of advanced programming language features such as type classes, which enable type-safeoperator overloading.

A lot of very fancy word, which hopefully will get clear along the road to enlightenment.

A guide to installation can be found here: https://www.haskell.org/platform/ to help you get started based on your OS.

Interactive Environment

Just like most programming languages, Haskell has an interactive environment which can be opened by typing: ghci

So let's play a bit with it:

➜  ~ ghci
GHCi, version 8.10.5: https://www.haskell.org/ghc/  :? for help
Prelude> a = 4
Prelude> b = 6
Prelude> c = a + b
Prelude> c
10

Nothing too crazy here, Haskell has all basic data types you can imagine: Boolean, Num, Char (a character wrapped in a single quote), String (any amount of characters in double quotes) and so on.

Types

There is a helpful command in GHCI which will give us the type of anything we give it:

Prelude> :t 'a'
'a' :: Char
Prelude> :t "abcd"
"abcd" :: [Char]

So we can see that 'a' is of type Char, and that "abcd" is of type [Char] meaning a list of Char, meaning a String. Let's check if it's true though, maybe I'm leading you on.

We are going to use lists to verify it:

Prelude> let a = ['a', 'b', 'c']
Prelude> :t a
a :: [Char]
Prelude> let b = "abc"
Prelude> :t b
b :: [Char]

Ok, same types, fine. But is it really the same ?

Prelude> a == b
True

There you have it.

Functions

It is time to start writing proper Haskell code by using a file instead of typing every single instruction in ghci. Open a file an call it fn.hs, preferably in the same directory where you launched ghci, otherwise I suggest you CTRL+D the current one and re-launch it where your file is.

We are going to write the following:

powerOfTwo x = x ** 2

You can then load your script in ghci instead of compiling it and running it (which will cover in a bit) by running:

Prelude> :l fn.hs
[1 of 1] Compiling Main             ( fn.hs, interpreted )
Ok, one module loaded.

As you can see, our file fn.hs as been loaded in the environment and we can now use it:

*Main> powerOfTwo 4
16.0
*Main> powerOfTwo 5
25.0
*Main> powerOfTwo 6
36.0

Looks good. How about a function to calculate the power of 3 now ? Let's edit our file fn.hs:

powerOfTwo x = x ** 2

powerOfThree x = x ** 3

Then we reload with the convenient:

*Main> :r
[1 of 1] Compiling Main             ( fn.hs, interpreted )
Ok, one module loaded.

Note that you have auto completion in ghci so you can write power then TAB and you should have the two functions listed below. Let's try our powerOfThree:

*Main> powerOfThree 2
8.0
*Main> powerOfThree 23
12167.0
*Main> powerOfThree 4
64.0

Looks good. But now, how about some refactoring to get a single function which gives us x to the power of y ? That way when the new requirements comes to say that we need a power of 7 and 42, you do not need to duplicate those lines. It should look something like this:

powerOfTwo x = x ** 2

powerOfThree x = x ** 3

powerOfX x y = x ** y

Then as usual to test our stuff:

*Main> :r
[1 of 1] Compiling Main             ( fn.hs, interpreted )
Ok, one module loaded.
*Main> powerOf
powerOfThree  powerOfTwo    powerOfX
*Main> powerOfX 2 3
8.0
*Main> powerOfX 2 2
4.0
*Main> powerOfX 2 7
128.0
*Main> powerOfX 7 2
49.0

Wunderbar, we reloaded our script, kept our previous functions (TAB on the second line after powerOf) and were able to try our new function with two parameters.

Now let's say that our function powerOfTwo and powerOfThree are called 1,000 times all over the code base, and it would be pretty hard to replace them all by powerOfx. But we still want to refactor it. We can do the following:

powerOfTwo x = powerOfX x 2

powerOfThree x = powerOfX x 3

powerOfX x y = x ** y

You will note that the order does not matter here, you can still declare powerOfX at the bottom even though it will be used higher up.

Main

Now, you do not want to remain in ghci anymore, being a senior developer and all, you want to compile your file and obtain a binary. This is how we would do it:

powerOfTwo x = powerOfX x 2 
powerOfThree x = powerOfX x 3 
powerOfX x y = x ** y

main = do
    putStrLn "Let's calculate some power !"
    print (powerOfX 2 2)

So a few things here:

  • If you want to run a program, you need to start with main = do this concept is similar to many programming languages. We will review this in details once we discuss I/O.
  • putStrLn is your usual function outputting a string in STDOUT followed by \n
  • When we call our function powerOfX, we first use print which is the same as the function above except no \n at the end. But the important part here are the parenthesis. There are no specific syntax in Haskell for function applications. Previously we simply used spaces to delimit our arguments but here we call print on powerOfX with two arguments so we need to explain what function is called with what parameters. Another way is to use the $ symbol like so:
print $ powerOfX 2 2

More explanation will be given in the next articles as it touches various concepts such as currying.

Finally, we can compile and call our program like so:

$ ghc --make fn.hs
[1 of 1] Compiling Main             ( fn.hs, fn.o )
Linking fn ...
$ ./fn            
Let's calculate some power !
4.0

Note that there are many different compilation flags you can use with ghc, more details can be found in their documentation.

Conclusion

I hope it was not too hard, even though I can see some of you sleeping in the back. In the next article we will discuss function definitions (we only saw implementations) and lists, and do some code comparisons with Python.

If you have a problem and no one else can help. Maybe you can hire the Kalvad-Team.