Functions
Functions
7de2c2c3
Martin Escardo authored 3 weeks ago
Functions in Haskell
• Read also Chapter 4 of the text book "Programming in Haskell.
Overview
In this lesson, we study various ways how functions can be defined in Haskell. We will study the following ways:
At the end, we will also look at operators (infix function symbols such as ++ ), and how to turn them into functions.
Composing functions
A video for this section, including explanation of the exercise, is here.
Exercise: Using the functions above, write a function that removes both the first and the last element of a list.
Conditionals
Haskell provides if _ then _ else _ . It is typed Bool -> a -> a -> a , polymorphically.
This is difficult to read, however; guarded equations (see below) can be more pleasant to read. We will avoid conditionals.
Exercise: Read the discussion about if _ then _ else _ on the Haskell wiki.
Guarded equations
A video for this section, including explanation for the exercise, is here.
Guarded equations are an alternative to if _ then _ else _ expressions. They are often more readable:
Here, n >= 0 and otherwise are called guards; they are Booleans. The function returns the value after the first guard that evaluates to True .
Guarded equations are more convenient to use than if _ then _ else _ :
Exercise: Using guarded equations, write a function of type Int -> Int -> Bool that returns True if the first argument is greater than the second and
less than twice the second.
Pattern matching
Pattern matching analyses the input according to how it is built. The input is matched against a sequence of patterns; the first pattern that matches
determines the output of the function.
Overview
There are only two possibilities for what a Boolean value can be: True or False . It is hence sufficient to have patterns for these two cases:
On Booleans
One of the simplest patterns is to match for Booleans.
If the input is just one Boolean, there are only two patterns:
The last three patterns can be combined. Here, the wildcard pattern _ matches anything, and discards it:
There is a difference between these two versions: in the latter, if the first argument is False , then the second argument does not need to be evaluated:
False is returned immediately.
In the next example, the pattern b matches anything. However, in contrast to _ , we can use b on the right-hand side of = :
Non-exhaustive patterns
Consider the following example:
Answer: This is a non-exhaustive pattern, and isTrue False will raise an exception:
On tuples
If the function we are defining expects as input a tuple, we can match against the individual components:
We actually don't use y in the output of the function, so we can use fst (x,_) = x instead. Similarly,
Exercise: Write a function swap :: (a, b) -> (b, a) that swaps the elements of a pair.
On lists
See also this video.
All lists are built by prepending (:) , successively, elements to an existing list, starting with the empty list [] . That means, the list [1, 2, 3] has been
obtained as 1:[2,3] , etc. - [1, 2, 3] is short for 1:(2:(3:[])) . In other words, every list in [a] is either
We are not actually using x or xs in the output of the second pattern, so we can write the function more simply as
Note that the parentheses around _:_ in the second pattern are necessary!
We can write more complex list patterns. To return the second element of a list:
sndElem :: [a] -> a
sndElem (_:x:_) = x
Case expressions
The aforementioned patterns are special forms, for Booleans, and lists. The general form for such pattern matching is via case expressions:
Here, it is important that all the patterns are exactly aligned, i.e., the [] and (_:_) must start in exactly the same column.
Lambda expressions
Lambda expressions are nameless functions. They are particularly useful in higher-order functions, which will be discussed in a later lesson. A video
accompanying this section is here.
Lambda expressions are of the form \<input variables> -> <output> . For instance, we can define a function that returns its double as \x -> 2 * x .
Here, the input variable is indicated by the backslash \ . After the arrow -> , the output of the function is specified. ( \ stands for the greek letter λ
(lambda), see Wikipedia). Thus, the following definitions are equivalent:
Just like a pattern can ignore (part of) the input, a lambda expression can ignore its input
One important application of lambda expressions are higher-order functions, where functions are arguments to other functions. Consider
When a function has two arguments, such as (:) , we can write it infix, between its two arguments. A function that is used infix (hence necessary binary)
is called an operator.
Any binary function can be turned into an operator by enclosing it in backticks. E.g. div 7 2 can be written 7 `div` 2 .
Conversely, any operator can be used prefix by enclosing it in parentheses, e.g., (:) 1 [2,3] .
Every operator ⊗ with inputs of type a and b and output of type c gives rise to three sections:
(⊗) :: a -> b -> c . Here, (⊗) = \x y -> x ⊗ y .
(x ⊗) :: b -> c , where x :: a . Here, (x ⊗) = \y -> x ⊗ y .
(⊗ y) :: a -> c , where y :: b . Here, (⊗ y) = \x -> x ⊗ y .
Remarks:
An operator ⊗ by itself is not a valid Haskell expression: it needs to be used as a section, e.g., (⊗) .
Sections are useful when programming with higher-order functions (cf. later lesson.).
Exercises
(Adapted and expanded from the book "Programming in Haskell)
Define three variants of a function third :: [a] -> a that returns the third element in any list that contains at least this many elements, using
head and tail
list indexing !!
pattern matching
Define a function safetail :: [a] -> [a] that behaves like tail except that it maps [] to [] (instead of throwing an error). Using tail and
isEmpty :: [a] -> Bool , define safetail using
a conditional expression
guarded equations
pattern matching
See also
Summary
We have seen several ways to define functions: composition, conditionals, guard equations, pattern matching, lambda expressions.
When patterns are not exhaustive, functions raise an exception whenever no pattern matches. To avoid this, one may use a catch-all otherwise
pattern at the end.
Any pattern matching can be expressed using a case expression.
Anonymous functions can concisely be written using lambda expressions.