Function Application: Using the Dollar Sign ($)
Things have been a little quiet here on the blog lately. We've got a lot of different projects going on behind the scenes, and we'll be making some big announcements about those soon! If you want to stay up to date with the latest news, make sure you subscribe to our mailing list! You'll get access to our subscriber-only resources, including our Beginners Checklist and our Production Checklist!
The next few posts will include a video as well as a written version! So you can click the play button below, or scroll down further to read along!
Let's talk about one of the unsung heroes, a true foot soldier of the Haskell language: the function application operator, ($)
. I used this operator a lot for a long time without really understanding it. Let's look at its type signature and implementation:
infixr 0
($) :: (a -> b) -> a -> b
f $ a = f a
For the longest time, I treated this operator as though it were a syntactic trick, and didn't even really think about it having a type signature. And when we look at this signature, it seems really basic. Quite simply, it takes a function, and an input to that function, and applies the function.
At first glance, this seems totally unnecessary! Why not just do "normal" function application by placing the argument next to the function?
add5 :: Int -> Int
add5 = (+) 5
-- Function application operator
eleven = add5 $ 6
-- Same result as "Normal" function application
eleven' = add5 6
This operator doesn't let us write any function we couldn't write without it. But it does offer us some opportunities to organize our code a bit differently. And in some cases this is cleaner and it is more clear what is going semantically.
Grouping with $
Because its precedence is so low (level 0) this operator can let us do some kind of rudimentary grouping. This example doesn't compile, because Haskell tries to treat add5
as the second input to (+)
, rather than grouping it with 6, which appears to be its argument.
-- Doesn't compile!
value = (+) 11 add5 6
We can group these together using parentheses. But the low precedence of ($)
also allows it to act as a "separator". We break our expression into two groups. First we add and apply the first argument, and then we apply this function with the result of add5 6
.
-- These work by grouping in different ways!
value = (+) 11 $ add5 6
value' = (+) 11 (add5 6)
Other operators and function applications bind more tightly, so can have expressions like this:
value = (+) 11 $ 6 + 7 * 11 - 4
A line with one $
essentially says "get the result of everything to the right and apply it as one final argument". So we calculate the result on the right (79) and then perform (+) 11
with that result.
Reordering Operations
The order of application also reverses with the function application operator as compared to normal function application. Let's consider this basic multiplication:
value = (*) 23 15
Normal function application orders the precedence from left-to-right. So we apply the argument 23 to the function (*)
, and then apply the argument 15 to the resulting function.
However, we'll get an error if we use $
in between the elements of this expression!
-- Fails!
value = (*) $ 23 $ 15
This is because ($)
orders from right-to-left. So it first tries to treat "23" as a function and apply "15" as its argument.
If you have a chain of $
operations, the furthest right expression should be a single value. Then each grouping to the left should be a function taking a single argument. Here's how we might make an example with three sections.
value = (*) 23 $ (+10) $ 2 + 3
Higher Order Functions
Having an operator for function application also makes it convenient to use it with higher order functions. Let's suppose we're zipping together a list of functions with a list of arguments.
functions = [(+3), (*5), (+2)]
arguments = [2, 5, 7]
The zipWith
function is helpful here, but this first approach is a little clunky with a lambda:
results = zipWith (\f a -> f a) functions arguments
But of course, we can just replace that with the function application operator!
results = zipWith ($) functions arguments
results = [5, 25, 9]
So hopefully we know a bit more about the "dollar sign" now, and can use it more intelligently! Remember to subscribe to Monday Morning Haskell! There will be special offers for subscribers in the next few weeks, so you don't want to miss out!