I wrote Python for the last 10 years, and I always tend to write code in a "functional" way -
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
Haskell (/ˈhæskəl/) is a general-purpose, statically typed, purely functional programming language with type inference and lazy evaluation. 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.
Just like most programming languages, Haskell has an interactive environment which can be opened by typing:
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.
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.
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
*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
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.
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 = dothis concept is similar to many programming languages. We will review this in details once we discuss I/O.
putStrLnis your usual function outputting a string in STDOUT followed by
- When we call our function
powerOfX, we first use
\nat 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
powerOfXwith 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.
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.
PS: Part 2 can be found here.
If you have a problem and no one else can help. Maybe you can hire the Kalvad-Team.