First impressions on Haskell and functional programming

Leonardo
4 min readMay 27, 2024

So I’ve decided to submit myself to a new challenge: learn Haskell and functional programming (properly, since I consider my current knowledge on it to be extremely superficial). Here are my first impressions on these topics.

First of all, you might be asking: “why Haskell?”. Well, Haskell is known to be a very consolidated and traditional programming language for teaching the fundamentals of functional programming. Besides, it is considered by many to be a purely functional language, so it is perfect for my purposes. I’ve also had no prior experience with programming languages focused on the functional paradigm.

Anyway, before running and compiling any Haskell program on my machine, I had to navigate to https://www.haskell.org to install the Haskell compiler and linker (GHC). The instructions were pretty clear and had no problems whatsoever installing a bunch of stuff on my Linux machine (I use Arch btw). I found it pretty cool that it comes with a REPL environment out of the box to test things (as an experienced Python developer myself, it is really nice to have this feature).

With that clarified, I’ve started with the most obvious program that everyone starts when learning a new language: “hello, world!” of course!

main = do
putStrLn "Hello, world!"

Without giving it too much thought on the syntax and on what’s going on under the hood, I’ve run “ghc hello-world.hs” and voilà, it generated the binary and I was able to run it smoothly. What I found interesting is that GHC performs, by default, compiling and linking in two distinct separate steps, generating two files in the process, different from how usually compiling a program in C or C++, for example, works. In general, you generate the obj files and perform the linking in the same step.

Ok, so now is when things start getting a bit weird. After that compiling and running my first Haskell program, I started studying how functions work in Haskell and how to write them. At first, for a function with a single argument, nothing too crazy happens, nor is the syntax very different from what a C family developer is accustomed to:

square :: Int -> Int
square n = n * n

So this is pretty straightforward, right? First, we have the function name declaration (square), which takes an integer (Int — the so-called “input”) and outputs another integer (Int). In the next line, we have the implementation of the function, which specifies the argument n and the return of the function (n * n).

When things get a bit weird (just a reminder — I’m writing from a developer acquainted with the procedural and objected-oriented programming perspective) is when we have functions which take more than 1 argument. Take for example an extremely simple function which sums two integers numbers x and y:

add :: Int -> Int -> Int
add x y = x + y

Let’s skip the first line for now. The second line is as straightforward as the first example: it provides an implementation for the add function, which takes arguments x and y and then returns their sum. Easy right?

So you might be asking now: what is going on in the first line? An input of integer that outputs to an integer, which then becomes the new input and subsequently outputs another integer? Well, you’re correct! In Haskell, and in other functional programming languages, there is this concept called “currying”. I’ll try to explain how it works.

Currying transforms a function that takes multiple arguments into a sequence of functions, each taking a single argument and returning another function that takes the next argument. This allows functions to be partially applied, meaning that a function that takes several arguments can be applied to some of those arguments and return a new function that only requires the remaining arguments.

In a language like Haskell, all functions are technically curried: they take one argument and return one result. If a function appears to take multiple arguments, it’s actually taking one argument and returning another function which takes the next argument, and so on. This might sound complex, but it leads to very powerful and flexible patterns in programming.

Back to our example: the type signature Int -> Int -> Int can be read more explicitly as Int -> (Int -> Int). This means add is a function that takes an Int and returns a function of type Int -> Int. Here’s how it operates:

- When you call add 5, you’re actually applying 5 to add, resulting in a new function of type Int -> Int which adds 5 to its argument.

  • You can then apply another integer to this resulting function, e.g., add 5 10 is evaluated as (add 5) 10 which finally returns 15.

There are several benefits from this concept:

  1. Partial Application: Currying makes partial application straightforward. For example, if you have a function that adds a particular number, you can create a new function by specifying the first number and reusing this function to add different numbers to the specified one.
  2. Higher-order Functions: Currying helps in creating higher-order functions that return other functions as results, making it easier to build abstract and flexible APIs.
  3. Function Composition: In functional programming, functions are often composed together (using one function’s output as another’s input). Currying facilitates this by allowing you to transform functions so that they match the expected input/output types for composition.

So this was my day 1 of programming in Haskell and I feel that I’ve learned quite a lot already. It was really fun and I look forward to learn more about Haskell and functional programming! Feel free to share your thoughts.

You can follow my Haskell journey on this GitHub repo: https://github.com/araujo88/haskell-tutorials

Getting started with Haskell: https://www.haskell.org/get-started/#set-up-a-haskell-development-environment

--

--

Leonardo

Software developer, former civil engineer. Musician. Free thinker. Writer.