Haskell is functional language where data is immutable. This means that regular for-loops don’t really exist. Looping however is very common pattern in programs in general. Here are some patterns how to do that in Haskell.
Recursion
Calculating Fibonacci numbers is common example (sort of like hello world in Haskell). There’s many different implementations at https://wiki.haskell.org/The_Fibonacci_sequence if you’re interested on having a look.
Simple recursive definition:
fibs :: Integer -> Integer
fibs 0 = 0
fibs 1 = 1
fibs n = fibs (n-1) + fibs (n-2)
When called with 0 result is 0. When called with 1 result is 1. For all other cases, fibs is called with values n-1 and n-1 and the results are summed together. This works fine when n is small, but calculation gets slow really quickly with bigger values.
Another way is to define list of all Fibonacci numbers recursively:
allFibs :: [Integer]
allFibs = 0 : 1 : zipWith (+) allFibs (tail allFibs)
Here a list is constructed. First element is 0, second element is 1 and rest of the list is obtained by summing the list with its tail (everything but the first element of the list). Definition is recursive and defines all Fibonacci numbers. However, Haskell doesn’t evaluate whole list, but only as much of it as is required.
Common pattern of processing elements in a list, producing a new list:
addOne :: [Integer] -> [Integer]
addOne [] = []
addOne (x:xs) = x + 1 : addOne xs
Two cases, when called with an empty list [], result is empty list. For all other cases, list is taken apart (x:xs), x contains first element of the list and xs is rest of the list. Body of the function creates a new list where head is x + 1 and tail is addOne xs. This processes whole list of Integer by adding one to each value. It also reverses the list.
Second common pattern is processing a list and reducing it to a single value:
sumAll :: Integer -> [Integer] -> Integer
sumAll n [] = n
sumAll n (x:xs) = sumAll (n + x) xs
If given list is empty (the terminal case), result is n. Second case again takes list apart (x:xs), adds x and n together and recursive call sumAll with tail of the list.
This common pattern is discarding some elements of a list:
evenOnly :: [Integer] -> [Integer]
evenOnly [] = []
evenOnly (x:xs) =
if even x
then x : evenOnly xs
else evenOnly xs
Again, result of empty list is just empty list. In all other cases we first check if x is even. If so, new list is constructed where head is x and tail is evenOnly xs. If x isn’t even, it’s discarded and evenOnly is called recursively with tail of the list.
More tools
Writing recursion by hand gets tedious and sometimes confusing (if you listened to the show, you probably noticed how I got confused and had to check that evenOnly actually works as I thought it would). For that reason, there are tools that abstract these common patterns and given them names.
First is map. It applies given function to each element of a list, thus producing a new list:
> map (+1) [1..10]
[2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
> map odd [1..10]
[True, False, True, False, True, False, True, False, True, False]
Second is fold. There is good article at https://wiki.haskell.org/Foldr_Foldl_Foldl%27 that talks about differences