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.