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:
>>> 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
>>> 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:
>>> 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
>>> 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
.