The dollar sign, $
, is a controversial little Haskell operator. Semantically, it doesn’t mean much, and its type signature doesn’t give you a hint of why it should be used as often as it is. It is best understood not via its type but via its precedence. We believe it is the relative semantic emptiness of this operator combined with the relative obscurity of precedence that makes it so confusing at first glance.
Function application
The $
operator is an infix operator for … function application?
($) :: (a -> b) -> a -> b
Given an a -> b
function and an a
to apply it to, it gives us a b
.
λ> sort $ "julie"
"eijlu"
Weird infix, but okay. Haskell doesn’t need an operator for function application; white space is enough.
f :: a -> b
x :: a
$ x = f x f
λ> sort "julie"
"eijlu"
This seems utterly pointless, until you look beyond the type.Here and throughout this article, we have simplified the type from what you may see if you query this information in your own REPL. The extra bits of information the REPL may give are not important to understanding this, so we’ve ignored them.
λ> :info ($)
($) :: (a -> b) -> a -> b
-- Defined in ‘GHC.Base’
infixr 0 $
This little note, so easily overlooked, at the end holds the key to understanding the ubiquity of ($)
: infixr 0
.
infixr
tells us it’s an infix operator with right associativity.0
tells us it has the lowest precedence possible.
In contrast, normal function application (via white space)
- is left associative and
- has the highest precedence possible (10).
So the role of the $
operator is to give us function application with a different – opposite, really – associativity and precedence.
What that means is that you usually see the $
where standard function application wouldn’t have the necessary associativity and precedence for the context. And what that means is you usually see it used instead of parentheses to associate things that otherwise wouldn’t.
That will be more clear with some examples. Compare:
λ> sort "julie" ++ "moronuki"
"eijlumoronuki"
λ> sort $ "julie" ++ "moronuki"
"eiijklmnooruu"
In the first case, the application of sort
to “julie” is binding the tightest, so the argument to sort
is just the first string, “julie”, and the sorted string “eijlu” is the first argument to (++)
. In the second case, using $
changes the associativity, exactly as if we’d used parentheses:
λ> sort ("julie" ++ "moronuki")
"eiijklmnooruu"
So the argument to sort
is the concatenated two strings, instead of only “julie”.
Composition
One pattern where you see the dollar sign used sometimes is between a chain of composed functions and an argument being passed to (the first of) those.
λ> head . sort $ "julie"
'e'
This is a bit odd since we just said the $
is right associative; there’s really nothing to evaluate on the right. So, the key here is the precedence. We can’t do this:
λ> head . sort "julie"
It wouldn’t evaluate properly because
- the
(.)
operator has a precedence of 9, but - function application (
sort "julie"
) has higher precedence.
That means the application of sort
to its argument would happen before the composition of head
and sort
. The (.)
expects two function arguments:
(.) :: (b -> c) -> (a -> b) -> a -> c
But applying sort
to an argument means it’s not a function anymore.
λ> :type (sort "julie")
(sort "julie") :: [Char]
The second argument to (.)
can be sort
, but it cannot be sort "julie"
; sort
is an a -> b
function but sort "julie"
is not.
But, weirdly, this isn’t the only way $
can be used with regard to function composition. In some cases $
can replace the composition operator, (.)
. So, both of these are valid Haskell:
λ> head . sort $ "julie"
'e'
λ> head $ sort "julie"
'e'
In the first case, it’s the 0-precedence of $
that matters most; in the second case, it’s the right-associativity that matters. In the second case, everything on the right gets evaluated first, and then head
applies to that result. So in this case we end up with the same result either way.
However, they cannot always be used interchangeably. These are both fine:
λ> headSort xs = head $ sort xs
λ> headSort "julie"
'e'
λ> headSort = head . sort
λ> headSort "julie"
'e'
But this is not:
λ> headSort = head $ sort
In this case, for once, it’s because of the type, not because of the precedence. sort
by itself is not the right type of thing to be the second argument to $
.
Large args
There are times when the dollar sign is used before a particularly large argument to a function. This is most common, perhaps, with do
blocks, but also shows up at times when there is a biggish anonymous function being passed as an argument to another function. Let’s look at a really typical example; this is from the documentation for the scotty
Beam me up. web framework.
We’ve rearranged the layout a tiny bit to better match our Type Classes do
-block aesthetics, but that’s otherwise straight from the docs. This short snippet has two $
operators. Both scotty
and get
each need some big action as their second argument, so the right-associativity of $
allows the whole big chunk after it to act as a single argument to the function. You could substitute parentheses for the exact same effect.
But typically Haskellers do not prefer to wrap such big chunks in parentheses.
Starting with GHC 8.6.1, there is also a language extension called BlockArguments
that can, if enabled, make the $
operator unnecessary in many of these contexts.
This is the same as the first two snippets. You may notice you need to keep the parentheses (or use $
) around the block that starts with get
because get
isn’t one of the keywords that works with BlockArguments
.
And a curiosity
Finally, we will note a curiosity about $
. Its type
($) :: (a -> b) -> a -> b
can also be written
($) :: (a -> b) -> (a -> b)
In fact, that is how the type should be read and understood, but because Haskell is curried by default, the second set of parentheses is unnecessary and, therefore, not usually written. But that means $
is just an identity function for … functions.
id :: a -> a
-- a ~ (a -> b)
id @(_ -> _) :: (a -> b) -> (a -> b)
That means, if you wanted, you could sometimes substitute the id
function for $
.The backticks around id
allow it to be used in infix position; putting backticks around normal prefix functions enables you to use them as infix functions, if you like.
λ> head $ sort "julie"
'e'
λ> head `id` sort "julie"
'e'
Of course, if you are infixing id
you could be prefixing it, right?
λ> id head (sort "julie")
'e'
And that is just the same as this:
λ> head (sort "julie")
'e'
You cannot, of course, do this in all the contexts you see $
used in, because infix id
and $
don’t have the same…you guessed it, associativity and precedence.When you make a prefix function, such as id
infix by backticking it but don’t otherwise specify the associativity and precedence for its infix use, it defaults to left-associativity and a precedence of 9, making it the same precedence as function composition but lower (by one) than normal prefix function application. You can substitute infix id
for the dollar signs in the scotty
examples, but you need to add some parentheses as well. We will leave understanding exactly where and why as an exercise for the reader.
While this little curiosity about the relationship of $
and id
is interesting, please do not start writing Haskell with a lot of infix id
. It will lead you to bad places and make your coworkers hate you.