Defining functions
Relevant content from Chapter 4 of Programming in Haskell 2nd Edition
New from old
Easiest way to define a new function is to use already existing ones.
even :: Integral a => a -> Bool
even n = n `mod` 2 == 0
splitAt :: Int -> [a] -> ([a]. [a])
splitAt n xs = (take n xs, drop n xs)
recip :: Fractional a => a -> a
recip n = 1 / n
Conditional expressions
Haskell provides multiple ways to define functions, that choose between a number of possible results. The simplest are conditional expressions
, which use a logical expression called condition
to choose between two results of the same type. If the condition
is True
, the first result is chosen, if it is False
then the second result is chosen instead.
abs :: Int -> Int
abs n = if n >= 0 then n else -n
Conditional expressions
may be nested, meaning it can contain additional conditional expressions
.
signum :: Int -> Int
signum n = if n < 0 then -1 else
if n == 0 then 0 else 1
Conditional expression
must always have an else
branch, which avoids the dangling else
problem.
Guarded equations
Alternative to conditional expressions
, in which a sequence of logical expressions called guards
is used to choose between a sequence of results of the same types.
abs n | n >= 0 = -1
| n == 0 = 0
| otherwise = 1
The symbol |
is read as such that and the guard otherwise
is defined in the standard prelude by otherwise = True
. Ending a sequence of guards with otherwise
is not necessary, but provides a convenient way of handling all remaining cases, as well as avoiding that none of the guards in the sequence is True
, which results in an error.
Main benefit of guarded equations
over conditional expressions
is that multiple guards are easier to read.
-- Conditional expressions
signum n = if n < 0 then -1 else
if n == 0 then 0 else 1
-- Guarded equations
signum n | n < 0 = -1
| n == 0 = 0
| otherwise = 1
Pattern matching
Sequence of syntactic expressions called patterns
is used to choose between a sequence of results of the same type.
not :: Bool -> Bool
not False = True
not True = False
Functions with multiple arguments can also be defined using pattern matching
, in which case patterns
for each argument are matched in order.
(&&) :: Bool -> Bool -> Bool
True && True = True
True && False = False
False && True = False
False && False = False
The definition can be simplified by combining the last three equation into a single one always returning False
, independent of the values. This can be done using the wildcard pattern _
that matches any value given.
True && True = True
_ && _ = False
This version has the benefit that under lazy evaluation
, if the first argument is False
, then the second one does not need to be evaluated.
True && True = True
False && _ = False
Prelude defines &&
like this, that is if the first argument is True
, then the result is the value of the second argument. If the argument is False
then the result is False
.
True && b = b
False && _ = False
Note that using the same name to be used for more than one argument in a single equation is not permitted.
Tuple patterns
Is a pattern, that matches any tuple of the same arity
, whose component all match the corresponding patterns in order.
The library functions fst
and snd
, they respectively select the first and second components of a pair.
fst :: (a, b) -> a
fst (x, _) = x
snd :: (a, b) -> b
snd (_, y) = y
List patterns
Is a pattern, that matches any list of the same length
, whose elements all match the corresponding patterns in order.
The function below called test
decides if a list contains precisely three characters beginning with the letter a
.
test :: [Char] -> Bool
test [`a`, _, _] = True
test _ = False
Lists are not primitive as such, but are instead constructed one element at a time.
Starting from the empty list []
using an operator :
called cons that constructs a new list by prepending a new element to the start of an existing list.
[1, 2, 3]
= {list notation}
1 : [2, 3]
= {list notation}
1 : (2 : [3])
= {list notation}
1 : (2 : (3 : []))
Meaning [1, 2, 3]
is an abbreviation for 1 : (2 : (3 : []))
. The cons operator is assumed to operate to the right. 1 : 2 : 3 : []
means 1 : (2 : (3 : []))
.
The cons operator can be used to construct patterns, which match any non-empty list whose first and remaining elements match the corresponding patterns in order.
test :: [Char] -> Bool
test (`a`:_) = True
test _ = False
head :: [a] -> a
head (x:_) = x
tail :: [a] -> [a]
tail (_:xs) = xs
Cons patterns must be parenthesized, because function application has a higher priority than all other operators.
Lambda expression
Allows constructing functions, as an alternative to defining functions. Compromises a pattern for each of the arguments, a body that specifies how the result is calculated, but does not give a name to the constructed function itself.
\x -> x + x
The symbol \
represents the Greek letter lambda
.
Despite the fact that lambda expressions
do not have names, they can still be used in the same way as any other function
> (\x -> x + x) 2
4
Firstly, they can be used to formalize the meaning of curried function
definitions.
-- Curried function
add :: Int -> Int -> Int
add x y = x + y
-- Lambda expression
add' :: Int -> (Int -> Int)
add = \x -> (\y -> x + y)
Makes precise that add
is a function that takes an integer x
and returns a function, that function in turn takes another integer y
and returns the result x + y
.
Secondly, they are useful when defining functions that return functions as results by their very nature, rather than as a consequence of currying.
-- Curried function
const :: a -> b -> a
const x _ = x
-- Lambda expression
const :: a -> (b -> a)
const x = \_ -> x
Finally, they can be used to avoid having to name a function that is only referenced once in a program.
odds :: Int -> [Int]
odds n = map f [0..n-1]
where f x = x * 2 + 1
Locally defined function f
is only referenced once, therefore the definition can be simplified.
odds :: Int -> [Int]
odds n = map (\x -> x * 2 + 1) [0..n-1]
Operator sections
Functions that are written between their two arguments (+
, *
, …) are called operators
. Any function with two arguments can be converted into a operator
by enclosing the name of the function in a single back quote \
`.
The opposite is possible as well any operator
can be converted into a curried function
by enclosing the name of the operator
in parentheses ()
.
-- Prefix notation is used for function application
max 7 2
-- Infix notation can be done using operators (+)
7 + 2
-- Functions can be converted into operators using backticks
7 `div` 2
-- Operators can be converted into functions using brackets
(+) 7 2
Moreover, this also allows one of the arguments to be included in the parentheses if desired ((1 +) 2
, (+ 2) 1
).
In general this also allows the expressions of the form (#)
, (x #)
, and (# y)
, for arguments x
and y
are called sections
.
(#) = \x -> (\y -> x # y)
(x #) = \y -> x # y
(# y) = \x -> x # y
Sections can be used to construct a number of simple but useful functions, in a particular compact way.
(+) = \x -> (\y -> x + y)
(1+) = \y -> 1 + y
(1/) = \y -> 1 / y
(*2) = \x -> x * 2
(/2) = \x -> x / 2
Furthermore, they are necessary when stating the type of operator, because an operator itself is not a valid expression.
(+) :: Int -> Int -> Int
Finally, they are necessary when using operators as arguments to other functions.
sum :: [Int] -> Int
sum = foldl (+) 0