My first-time pure functional programming

25 July 2022[updated]

6 min read

Functional programming

Yesterday I had my first experience with pure functional programming. I heard this term, functional programming, nearly a year ago. Since then I became fascinated by the concept but I never dug deep into the paradigm.

This semester I decided to enroll in a course on principles of programming languages. I am now learning Haskell, and specifically the pure functional programming paradigm at its core.

Yesterday was my first coding session with functional programming. I report here my experience as a reminder for myself and as a source of inspiration for newcomers.

It was not easy, but it was fun.

Installation

I skip the installation details. I am on Windows. What I did was going to Haskell, in the Downloads section, and follow the instructions. There are also plenty of videos on Youtube, so this should be the easy part.

Coding exercises

I found an interesting website here. It contains 12 weeks of lessons and homework for each week. Every homework is tailored for the knowledge that you should have acquired if you followed the previous weeks' lessons. Perfect, I started from here.

Task 1 - Reasoning as a javascript developer

The first task I tackled is:

Double the value of every second digit beginning from the right. That is, the last digit is unchanged; the second-to-last digit is doubled; the third-to-last digit is unchanged; and so on.

For example, [1, 3, 8, 6] becomes [2, 3, 16, 6].

https://www.seas.upenn.edu/~cis194/spring13/hw/01-intro.pdf

Now, I read somewhere that the first and most important step is to define the input and output types of the function. The types will help you reason on the problem and (hopefully) understand better the solution.

Coming from javascript and dart, I knew of the map function. Specifically, the map with index. I also knew that also Haskell has the map function, but the signature does not provide the index of the element.

My idea was to use the index to compute if the current position is even or odd and applying the doubling when needed. So, my first task became creating a map indexed function in Haskell.

I copied the type signature of the map function in Haskell:

(a -> b) -> [a] -> [b]

I then created a helper function that takes also the initial index as input.

The function has three arguments:

  1. The mapping function, from the element a of the source list and the index position Int to the mapped element b of the resulting list
  2. The initial index as Int
  3. The source list to map [a]
mapIndexedManual :: (a -> Int -> b) -> Int -> [a] -> [b]
mapIndexedManual f n [] = []
mapIndexedManual f n (x : xs) = f x n : mapIndexedManual f (n + 1) xs

Using this function I was able to write the map indexed by passing an initial index value of 0:

mapIndexed :: (a -> Int -> b) -> [a] -> [b]
mapIndexed f [] = []
mapIndexed f l = mapIndexedManual f 0 l

Using this function it becomes easy to complete the first exercise of the homework:

doubleEven :: [Int] -> [Int]
doubleEven n = mapIndexed (\x i -> if i `mod` 2 == 0 then (x + x) else x) n

List of the concepts used:

Task 2 - Function composition

Add the digits of the doubled values and the undoubled digits from the original number.

For example, [2, 3, 16, 6] becomes 2 + 3 + 1 + 6 + 6 = 18.

https://www.seas.upenn.edu/~cis194/spring13/hw/01-intro.pdf

This is a more technical problem. I have some memories of my programming 101 classes in which we reasoned about how to sum the digits of a number.

The first function I wrote is recursive. It sums the digits of a given Int:

sumDigits :: Int -> Int
sumDigits n = if n < 10 then n else ((sumDigits (n `mod` 10)) + (sumDigits (n `div` 10)))

I had some problems with the div function. I tried to use the standard / but it caused problems with the type conversion. It took me some time to end up using the div.

This function is recursive. It sums directly single digits numbers (n < 10) and otherwise extracts each digit in a recursive call.

That was the difficult part of the second exercise. From there I just wrote some more functions to apply the sumDigit to each element of a list and then sum the elements of the list:

sumList :: [Int] -> Int
sumList [] = 0
sumList (x : xs) = x + sumList xs

addDigits :: [Int] -> [Int]
addDigits [] = []
addDigits l = map sumDigits l

addSumDigits :: [Int] -> Int
addSumDigits l = sumList (addDigits l)

Here I noticed one interesting result of the functional programming paradigm. I was forced to write single self-contained functions and reason about each of them. Then the solution would simply be a composition of smaller pure function.

In the example, addSumDigits becomes the composition of sumList and addDigits, that itself used the sumDigits function written above.

List of the concepts used:

Task 3

Calculate the remainder when the sum is divided by 10. For the above example, the remainder would be 8. If the result equals 0, then the number is valid.

https://www.seas.upenn.edu/~cis194/spring13/hw/01-intro.pdf

By this point, I became more accustomed to the process of writing pure functions. Using the functions of task 1 and task 2, the solution to the full problem became straightforward:

checkValid :: Int -> Bool
checkValid n = n `mod` 10 == 0

allInOne :: [Int] -> Bool
allInOne n = checkValid (addSumDigits (doubleEven n))

Once again, we notice how the final complete function is just a composition of the other functions. Since all the functions are pure, the output of allInOne will always be the same given the same input.

Furthermore, everything is type-safe; the compiler will not allow you to pass incompatible parameters. Therefore, we can be sure that the function is correct (given that we did not make any logic mistakes in the other functions).

Conclusion

Writing functional code is not easy. Years of imperative programming makes it even more uncomfortable. Nonetheless, you feel satisfied when you are finally able to complete the puzzle.

It took me about 3 hours to write those functions. Most of the time I spent thinking about what types my function should have based on the solution I had in mind.

It had a lot of fun overall. I know that there is a lot (a lot) more in functional programming and Haskell. Anyhow, I am happy I was able to complete the exercises successfully.