Skip to content

A Special Function Application Operator

The final operator for functions that we need to discuss in this section is an odd one, because it doesn't seem to do anything:

($) :: (a -> b) -> a -> b
f $ x = f x

So f $ x applies the function f to x, which explains why ($) is called the function application operator. But why wouldn't we simply write f x instead of f $ x. It seems a pretty useless thing, this operator. Let's look at a somewhat contrived example that is representative of typical programming patterns thta we encounter quite often.

Let's say we want to compute the expression 3 * (1 + 2 * 5) but only with the help of two functions,

add      x y = x + y
multiply x y = x * y

We can do this quite easily:

GHCi
>>> add x y = x + y
>>> multiply x y = x * y
>>> multiply 3 (add 1 (multiply 2 5))
33

It's certainly a matter of taste, but I don'l like this accumulation of parentheses. Moreover, what we really have is a pipeline. The value of multiply 2 5 gets passed to add 1. The result of that gets passed to multiply 3, and the result is the final value of the expression. Using the function application operator, we can write the expression as

GHCi
>>> multiply 3 $ add 1 $ multiply 2 5
33

This eliminates the parentheses and makes the pipeline structure of the whole expression more explicit. I admit that all these $ signs aren't all that pretty to look at either, but I nevertheless find this expression easier to parse than the one with parentheses. In some programs, we build even longer pipelines. Breaking them up using $, possibly even putting each stage of the pipeline on its own line, makes the data flow of the program easy to discern, even if the pipeline has 10 stages. Imagine an expression with 10 pairs of nested parentheses instead. I prefer the pipeline.

But how does it work? Clearly, this doesn't work:

GHCi
>>> multiply 3 add 1 multiply 2 5

<interactive>:83:1: error:
    • Non type-variable argument in the constraint: Num (a -> a -> a)
      (Use FlexibleContexts to permit this)
    • When checking the inferred type
        it :: forall {a}.
              (Num a, Num (a -> a -> a),
               Num ((a -> a -> a) -> (a -> a -> a) -> a -> a -> a)) =>
              a

This would say that we try to apply multiply to six arguments and not all of them are numbers, whereas multiply takes two numbers as arguments. The trick is that the ($) operator has the lowest possible precedence. So any expression involving $ is first split into the part to the left of the $ sign and the part to the right of the $ sign. Both expressions are evaluated recursively. The left hand side needs to produce a function. The right hand side needs to produce a value whose type matches the argument type of the function produced by the left hand side. Here,

multiply 3

is a function of type Int -> Int. The right hand side of the first $ is add 1 $ multiply 2 5. This gets broken up into add 1 as the left hand side, which is another function of type Int -> Int. The right hand side is multiply 2 5, which has type Int. Thus, this type matches the argument type of add 1. We apply add 1 to this value, producing an Int. Then we apply multiply 3 to this Int. This is exactly the same that would have happened using parentheses. Again, we'll encounter this function application operator repeatedly throughout this book.

Exercise

Define a simple function

nextChar :: Char -> Char

It takes a Char as an argument and returns the next character. So nextChar 'A' should return 'B'. nextChar '3' should return '4'.

To implement this function, you need a function to convert a character to its code point (e.g., the code point for 'A' is 65), add one to its return value, and then convert from the code point back to a character. The Data.Char module defines such functions. Use Hoogle to find out which functions Data.Char defines and which ones may be the right ones to use.

Avoid the use of parentheses in your function definition.

Solution
GHCi
>>> import Data.Char
>>> nextChar ch = chr $ ord ch + 1
>>> nextChar 'A'
'B'
>>> nextChar '8'
'9'

Exercise

We have two closely related operators now, the composition operator

(.) :: (b -> c) -> (a -> b) -> (a -> c)

and the function application operator

($) :: (a -> b) -> a -> b

When building a pipeline of function applications, it is a matter of personal preference to choose between f $ g $ h $ x and f . g . h $ x. Technically, these two expressions are different. The former applies h to x, g to the result, and f to the result of that. The latter constructs the function f . g . h and applies this function to x.

Prove that these two expressions always produce the same result.

Solution

By the definition of ($), we have

f $ g $ h $ x = f (g $ h $ x)
              = f (g (h $ x))
              = f (g (h x))

and

f . g . h $ x = (f . g . h) x

By the definition of (.), we have

(f . g . h) x = (f . g) (h x)
              = f (g (h x))

Put together, we have f $ g $ h $ x = f (g (h x)) = f . g . h $ x.