Learning Haskell — Day 3: the Elegance of Functional Programming

Leonardo
4 min read3 days ago

--

Three days into my Haskell journey, and I’m already captivated by the simplicity and power of its syntax, especially when it comes to recursion and defining functions with precision and clarity. Haskell, being a purely functional language, offers a different perspective on solving problems, particularly in how it handles lists, mathematical computations, and even basic condition checks. Today, I will highlight some fundamental aspects of Haskell that have stood out in my learning process: recursion, guards in function definitions, local definitions, and using head and tail functions effectively.

Understanding Recursion in Haskell

Recursion is a cornerstone of functional programming, and Haskell utilizes it extensively to perform operations on lists and solve complex problems with elegant solutions. Unlike imperative languages where loops are the go-to for iteration, Haskell leans on recursion to navigate through data structures and computations.

Calculating List Length

Consider the task of determining the length of a list. In Haskell, this can be succinctly expressed using a recursive function:

listLength :: [a] -> Int
listLength [] = 0
listLength list = 1 + listLength (tail list)

Here, the function listLength checks if the list is empty ([]). If it is, it returns 0. If not, it adds 1 to the result of the recursive call listLength (tail list), which processes the rest of the list by removing the first element (head). This function beautifully demonstrates the use of recursion for list processing without the explicit loop management seen in other programming languages.

Power Function with Guards

Another powerful feature of Haskell is the use of guards in defining functions. Guards allow you to perform pattern matching along with condition checks, making the function definitions both clear and concise.

Consider a function to calculate `x` raised to the power of `n`:

power :: Integer -> Integer -> Integer
power x 0 = 1
power x n
| even n = y * y
| otherwise = y * y * x
where
y = power x (div n 2)

This function first checks if n is 0, returning 1 as any number to the power of 0 is 1. If not, it uses guards to check if n is even. If n is even, it calculates y * y; if odd, it calculates y * y * x. Here, y is computed as power x (div n 2), showing how Haskell’s where clause neatly encapsulates local bindings needed for computation, reducing redundancy and improving clarity.

Determining Prime Status with Recursion

Determining if a number is prime is a classic example to illustrate the recursive capabilities combined with guards:

isPrime :: Int -> Bool
isPrime 0 = False
isPrime 1 = False
isPrime x = not (hasDivisor (x - 1))
where
hasDivisor :: Int -> Bool
hasDivisor 1 = False
hasDivisor n = mod x n == 0 || hasDivisor (n - 1)

This function uses a helper function hasDivisor defined within a where block that recursively checks if any number less than x evenly divides x. It beautifully showcases how recursion, combined with logical operations, can lead to straightforward implementations of otherwise complex algorithms.

Simple and Elegant Fibonacci Sequence

The Fibonacci sequence is another example where Haskell’s recursive abilities shine:

fibonacci :: Int -> Int
fibonacci 0 = 0
fibonacci 1 = 1
fibonacci n = fibonacci (n - 1) + fibonacci (n - 2)

This implementation directly reflects the mathematical definition of the Fibonacci sequence and is a testament to how recursion mirrors natural language in Haskell.

Reversing a String Recursively

Finally, reversing a string using recursion in Haskell demonstrates handling more than just numerical computations:

reverseString :: String -> String
reverseString [] = []
reverseString (x : xs) = reverseString xs ++ [x]

This function takes a string (or a list of characters), and recursively moves the head of the list to the end until it reaches an empty list. The recursion builds the reversed string in a clear and intuitive manner.

Conclusion

Day 3 of my Haskell adventure has reinforced my appreciation for the language’s capacity to handle complex tasks with concise, clear, and effective code. Whether it’s recursion, guards, or simple list manipulations, Haskell continues to impress me with its capability to elevate code readability and maintainability. Each example today not only demonstrated Haskell’s syntactic elegance but also its profound ability to facilitate powerful programming paradigms through simple constructs.

I invite you to follow my progress and engage with the fascinating world of functional programming through my ongoing GitHub repository.

Stay tuned for more insights as we uncover further aspects of Haskell in the days to come!

--

--

Leonardo

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