Take the example functions below (pure and IO) which both return a capitalized string.

Pure function:

capitalize :: String -> String

capitalize [] = []

capitalize (x : xs) = toUpper x ++ capitalize xs

Magnification of the pure function:

Type signature

capitalize :: String -> String indicates that the capitalize function takes a string as input and returns a string. Stating the type signature is not mandatory for most cases because Haskell has the ability to perform type inference but it is recommended as a good practice as it is also necessary for documentation.

2. Pattern Matching

capitalize []

capitalize (x : xs)

String is just syntactic sugar for [Char] , that is, a String is a list of characters. The list data type has two constructors: the empty case and the cons case written as data [] a = [] | a : [a] ((x : xs) translates into x as a head and xs as a tail). The list type has two possible values, the empty list and the non-empty list and it is therefore necessary for us to state all possible return values, hence pattern matching. Data types will be expounded more in the next piece so don’t worry too much about that right now.

Pattern matching consists of specifying patterns to which some data should conform and then checking to see if it does and deconstructing the data according to those patterns — Miran Lipovača

As you would expect, calling a function such as capitalize on an empty list would return an empty list, because there is no data to manipulate. The same case is also known as the edge condition .

The next case caters for an actual list of characters and in that case we notice the all powerful recursion .

3. Recursion

capitalize (x : xs) = toUpper x ++ capitalize xs

As earlier stated, purely functional languages are about telling the computer what data is instead of how to reach some result; and recursion is very important and necessary to achieve this. Another reason why recursion is important in this case is because the toUpper function takes a single character as input and produces that character capitalized. It’s signature is written as toUpper :: Char -> Char .

Recall that for the capitalize function, the input and output are both lists of characters, therefore toUpper performed on one character is not sufficient and we can not perform toUpper on the whole string because that will produce a type error! Recursion is therefore our ideal solution in this instance. We concatenate the result of toUpper on the head of the list and the result of the recursive call.

*Lib> capitalize “dee”

“DEE”

The above function call is evaluated as:

capitalize “dee”

== toUpper 'd' ++ capitalize “ee”

== 'D' ++ capitalize “ee”

== 'D' ++ (toUpper 'e' ++ capitalize "e")

== 'D' ++ ('E' ++ capitalize "e")

== 'D' ++ ('E' ++ toUpper 'e' ++ capitalize [])

== 'D' ++ ('E' ++ 'E' ++ [])

== 'D' ++ "EE"

== "DEE"

The edge condition we mentioned earlier is very important if we want our recursive function to terminate. The program will still compile without it but produce such an unattractive output when the capitalize function is called:

"DEE*** Exception: src/Lib.hs:12:1-47: Non-exhaustive patterns in function capitalize

IO function:

capitalize' :: IO [Char]

capitalize' = do

str <- getLine

return (map toUpper str)

The signature of capitalize’ suggests that it is of type IO [Char] or IO String and therefore it returns an IO String action. We use the do block to bundle up multiple IO actions.

The getLine function is of type IO String and it is used to ‘pick’ input. Binding getLine to str is necessary to unbox the input received, that is unwrap String from IO String.

The map function has a signature (a -> b) -> [a] -> [b] . Haskell functions are said to take in only one argument but from the signature of the map function, we see that it takes at least two arguments: the function (a -> b) and a variable a . Say what now 😕? Contradictions, right? That will be clear in a minute.

Unveiling the trick: Higher order functions (HOF’s) are functions that have their input or output as a function. Curried functions which are a class of HOF’s are those that can take in more than one argument. Our map function is a good example of a curried function. Let us examine how.

We can re-write the signature of map as follows: ((a -> b) -> [a]) -> [b] and refer to it as a partially applied function, which is one that takes as many parameters as we left out.

Using partial application (calling functions with too few parameters, if you will) is a neat way to create functions on the fly so we can pass them to another function or to seed them with some data — Miran Lipovača