The Application and Composition Operators¶
The Composition Operator¶
Composing functions is a common and useful way to create new functions in Haskell. In mathematics, if you have two functions \(f(x)\) and \(g(x)\), you compute their composition as \(f(g(x))\). Often we don’t write the \(x\), and so can re-state this as follows: the composition of \(f\) and \(g\) is \(f \circ g\).
Haskell mimics this notation by using .
to compose functions. For
example:
> f :: Int -> Int
> f n = n + 1
>
> g :: Int -> Int
> g n = 2*n - 1
>
> fg = f . g -- fg is the composition of f and g
For example:
> f(g(3))
6
> fg(3)
6
What is the type of .
? Try to think about this a little bit first before
looking at the answer!
According to Haskell:
> :type (.) -- . must be written (.) to avoid a parser error
(.) :: (b -> c) -> (a -> b) -> a -> c
That’s quite a mouthful! To understand it, consider this function definition:
h = f . g
The signature for .
says that f
has the type b -> c
, g
has the
type a -> b
, and h
has the type a -> c
. The type for h
is not
in brackets so that it can be curried.
It is easiest to understand the type of f . g
by examining the expression
f (g x)
. If g
takes an input of type a
and returns an output of
type b
, then that means the input type to f
must be b
. The output
of f
can be any type c
. The input type of h
must match the input
type of g
, i.e. it must be a
. Similarly, h
must match the output
type of f
, so its output is c
.
Take some time to work through this and understand why all the types are what
they are. A good trick to remember is to start by giving g
(the second
input to .
) the type a -> b
.
One of the uses of .
in Haskell is to cut down on brackets. For example,
here are two equivalent ways to do the same calculation:
> sqrt (abs (negate 2)) -- writing -2 gives a parser error, so we use
1.4142135623730951 -- negate instead
> (sqrt . abs . negate) 2
1.4142135623730951
.
also lets us define functions more compactly. Compare these two
equivalent function definitions:
> root1 :: Float -> Float
> root1 x = sqrt (abs (negate x))
>
> root2 :: Float -> Float
> root2 = sqrt . abs . negate
root2
is written in what is sometimes called a point free style. All
it means is that the input parameter, x
in this case, is not written
explicitly.
The difference between function composition and regular function application can sometimes seem confusing. So it is helpful to remember this example:
-- Composition
f (g (h x)) -- these two expressions evaluate
(f . g . h) x -- to the same thing
-- Application
f g h x -- these two expressions evaluate
((f g) h) x -- to the same thing
-- (but probably not the same as the two
-- previous composition expressions!)
A function application like f g h x
is a call to the function f
, and
g
, h
, and x
are the parameters passed to it. The parameters are
passed in the order they are given, i.e. first g
is passed to f
, then
h
, and finally x
. Thus, function application is left-associative.
$: The Application Operator¶
The $
operator may seem unusual first since its definition is so simple:
($) :: (a -> b) -> a -> b
f $ x = f x
$
is called the function application operator, and if you read its
implementation you can see that, apparently, f $ x
is the same as f x
.
So what is the point of $
?
What makes $
useful is that it has the lowest possible precedence for any
infix operator. Recall that for infix operators it is necessary to assign a
precedence so we know in what order to evaluate them. For example, in math,
when we evaluate the expression \(2 + 3\cdot 4\), we know to evaluate the
\(\cdot\) first because multiplication has a higher precedence than
addition.
The :info
command will show you the precedence of an infix operator,
e.g.:
> :info ($) -- $ must be written ($) to avoid a parser error
($) :: (a -> b) -> a -> b -- Defined in `GHC.Base'
infixr 0 $
So if you see a $
in a Haskell expression, it is evaluated last because
it has such low priority. It essentially changes function application
right-associative instead of the (default) left-associative.
The main use of $
is to simplify some expressions by removing brackets.
For example:
> sum (map (+1) [1,2,3])
9
Brackets are necessary around the map
expression. If you left them out,
sum
would try to apply itself to map
, which results in an error:
> sum map (+1) [1,2,3]
Couldn't match expected type `[(a1 -> a1) -> [t1] -> t0]'
with actual type `(a0 -> b0) -> [a0] -> [b0]'
In the first argument of `sum', namely `map'
In the expression: sum map (+ 1) [1, 2, 3]
In an equation for `it': it = sum map (+ 1) [1, 2, 3]
But by using $
, we can make the function application right-associative,
and thus get rid of a pair of brackets:
> sum $ map (+1) [1,2,3]
9
Remember, the trick with $
is that it has the lowest possible precedence
of all infix operators, and so when you see it in an expression that means it
is evaluated last. So in this example map (+1) [1,2,3]
is evaluated
first, and then sum
is applied to its result — which is exactly how we
want to do things.