dollar sign

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

f $ x = f x
λ> 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 scottyBeam me up. web framework.

{-# LANGUAGE OverloadedStrings #-}
import Web.Scotty

import Data.Monoid (mconcat)

main = scotty 3000 $
    get "/:word" $
      do
        beam <- param "word"
        html $ mconcat ["<h1>Scotty, ", beam, " me up!</h1>"]

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.

{-# LANGUAGE OverloadedStrings #-}
import Web.Scotty

import Data.Monoid (mconcat)

main = scotty 3000
    (get "/:word"
      (do
        beam <- param "word"
        html $ mconcat ["<h1>Scotty, ", beam, " me up!</h1>"]))

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.

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE BlockArguments #-}
import Web.Scotty

import Data.Monoid (mconcat)

main = scotty 3000
    (get "/:word"
      do
        beam <- param "word"
        html $ mconcat ["<h1>Scotty, ", beam, " me up!</h1>"])

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.

Join Type Classes for courses and projects to get you started and make you an expert in FP with Haskell.