The $ Operator

The $ Rule

In the past, I was confused by the $ operator. It would sometimes appear in other people's programs (never my own, of course, since I didn't understand it), and seemed to be doing something quite magical. Then I devised The $ Rule.

The $ Rule

The $ operator acts as a left parenthesis with an implied right parenthesis at the end of the expression.

That's it. Nothing magical at all. It's just an alternative to using parentheses for grouping.

(As I later discovered, The $ Rule doesn't quite tell the whole story. We will get to that story before the end of this tutorial. But The $ Rule covers the vast majority of uses of the $ operator, so it's a good rule of thumb if you're as puzzled as I was.)

Examples

Let's look at some examples of the $ operator and The $ Rule in practice. Suppose we need a function which will extract a bare file name from a FilePath, and fold it to lower case. We want to write filefold :: FilePath -> String, so that e.g. filefold "/etc/README""readme". Here's our first version, filefold0.hs.

import Data.Char (toLower)

filefold0 fp = map toLower (reverse (takeWhile ('/' /=) (reverse fp)))

Maybe this looks a bit cluttery with all those parentheses, so let's see if the $ operator can help us. At the end of the expression are 3 right parentheses: we can eliminate them by replacing each corresponding left parenthesis with a $ operator. This yields filefold7.hs.

import Data.Char (toLower)

filefold7 fp = map toLower $ reverse $ takeWhile ('/' /=) $ reverse fp

Note that we cannot eliminate the parentheses in ('/' /=). In terms of The $ Rule, this is because the right parenthesis is not at the end of the expression. (More accurately, the parentheses here are a special syntax that creates a section, and are not just for grouping.)

It's a matter of taste whether you prefer filefold0 or filefold7: they are exactly equivalent. In this case, I personally don't think the $ operator does anything to improve clarity. Perhaps we were too greedy: for fewer dollars we can get filefold4.hs, which emphasizes that the transformation takes place in two parts (one of which involves several functions).

import Data.Char (toLower)

filefold4 fp = map toLower $ reverse (takeWhile ('/' /=) (reverse fp))

Altogether, for these 3 nested functions there are 8 ways to split up the work between dollars and parentheses. All are illustrated in filefolds.hs. I think the clearest are filefold0 and filefold4; their duals - filefold7 and filefold3 respectively - also have something to commend them. The remainder are perverse.

import Data.Char (toLower)

filefold0 fp = map toLower (reverse (takeWhile ('/' /=) (reverse fp)))
filefold1 fp = map toLower (reverse (takeWhile ('/' /=) $ reverse fp))
filefold2 fp = map toLower (reverse $ takeWhile ('/' /=) (reverse fp))
filefold3 fp = map toLower (reverse $ takeWhile ('/' /=) $ reverse fp)
filefold4 fp = map toLower $ reverse (takeWhile ('/' /=) (reverse fp))
filefold5 fp = map toLower $ reverse (takeWhile ('/' /=) $ reverse fp)
filefold6 fp = map toLower $ reverse $ takeWhile ('/' /=) (reverse fp)
filefold7 fp = map toLower $ reverse $ takeWhile ('/' /=) $ reverse fp

Of course, there are yet more ways to skin this cat. We can emphasize the two parts of the transformation even further by introducing a new name, as in filefoldn.hs.

import Data.Char (toLower)

filefoldn fp = map toLower f
    where f = reverse (takeWhile ('/' /=) (reverse fp))

Or we could use function composition to eliminate all names (and $ operators), as in filefold.hs. This is the least cluttered version of all, and would, I think, be chosen by most seasoned Haskell programmers. It's definitely not the first version I'd devise, though!

import Data.Char (toLower)

filefold = map toLower . reverse . takeWhile ('/' /=) . reverse

More Examples

Back to the $ operator. The judicious use of the $ operator to separate semantically distinct parts of a complex expression (as in filefold4) seems promising. Consider putStr and friends, which take a string argument. Except in the simplest cases, the argument must be surrounded by parentheses, as in calc0.hs.

main = do
  putStr "Type a number: "; x <- readLn
  putStr "and another: "; y <- readLn
  let s = x + y
  putStrLn (show x ++ " + " ++ show y ++ " = " ++ show s)

A small but definite improvement is to use the $ operator to eliminate the parentheses around the argument to putStrLn. This gives us calc1.hs.

main = do
  putStr "Type a number: "; x <- readLn
  putStr "and another: "; y <- readLn
  let s = x + y
  putStrLn $ show x ++ " + " ++ show y ++ " = " ++ show s

The more complicated the expression, the more compelling the use of the $ operator. There are certain functions that take an IO action as an argument, for example when (and unless). An IO action might be as simple as a call to putStrLn:

when debug (putStrLn "initialization started")
initialize
when debug $ putStrLn "initialization completed"

For a simple case like this, there's not much to choose between parentheses and the $ operator. But an IO action could equally well be a multiline do expression:

when debug $ do
    h <- openFile "debug.out" appendMode
    hPutStrLn h "dump of parse tree"
    hPutStr h (show parseTree)
    hClose h

Now the $ operator is in its element: we could simply surround the entire do expression in parentheses, but it is much neater to avoid a "dangling" right-paren on the last line. This style is common also in GUI programming: here is an example from the gtk2hs package:

renderWithDrawable win $ do
  scale (realToFrac width'  / realToFrac width)
        (realToFrac height' / realToFrac height)
  svgRender svg

Another idiomatic use of the $ operator is in constructors. Rather than Just (x + y), we can write Just $ x + y.

The $ Rule revisited

Consider showsum0.hs.

showSum x y = show x ++ " + " ++ show y ++ " = " ++ show (x + y)

It would seem from our description of The $ Rule so far that we could eliminate the parentheses in show (x + y), as in showsum1.hs.

showSum x y = show x ++ " + " ++ show y ++ " = " ++ show $ x + y -- wrong!

But this results in a type error in " = " ++ show. The problem is that we've been focusing on what happens to the right of the $ operator, but it also affects what happens to its left. We could patch up The $ Rule, but it gets rather unwieldy.

The $ Rule Improved(?)

The $ operator acts as right parenthesis with an implied left parenthesis after the preceding $ operator -- if any, otherwise the beginning of the expression -- followed by a left parenthesis with an implied right parenthesis at the end of the expression.

Now we can see what went wrong in the previous example. Applying the new rule to this expression yields showsum2.hs, and it is clear why this is not doing what we wanted: the $ operator has grouped together everything to its left, as well as everything to its right.

showSum x y = (show x ++ " + " ++ show y ++ " = " ++ show) (x + y) -- wrong!

Our new version of The $ Rule is correct, but I find it unhelpfully complicated. Perhaps, now that we've got a feel for how the $ operator works in practice, we should revisit the theory.

The $ Operator, Really

We've been talking about the $ operator as though it were some kind of syntactic sugar, but in fact it's not special syntax at all. It's defined in the Prelude like this:

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

The first line states that $ is a right associative operator with precedence 0. Right-associative means that a $ b $ c parses as a $ (b $ c). Precedence 0 is the lowest precedence, and means that, for any operators and , the expression a b $ c d parses as (a b) $ (c d).

The second line gives the type of the $ operator. It takes two arguments, the first argument is itself a function f, which can be of any type at all, so long as it has just one argument. The second argument to $ must be of f's input type. The return type of the overall expression is the same as the return type of f.

The third line defines what $ actually does: it applies its first (left) argument, the function f, to its second (right) argument.

It's the third line that caused me confusion: it seems to be saying that the $ operator doesn't do anything at all! Apparently, wherever we have f $ x, we can replace it with f x. And this is true, provided f and x are sufficiently simple. But when the expressions become more complicated - involving other operators or function application - the low precedence and right associativity of the $ operator, given by the first line of its definition, come into play.

Seen from the point of view of the $ operator, since it has the lowest possible precedence, any expression either to its left or to its right will be "gathered up" to form the operator's arguments. For exactly the same reason, 3 + 4 * 523 (and not 35) because the lower precedence + gathers up the entire expression 4 * 5. Generally, such gathering up is more useful to the right of the operator, and conveniently the $ operator is right-associative, so what it gathers up on its right will include any other $ operators.

I've realized that the various phrases used to describe precedence induce a slight cognitive dissonance in me. Compared to a lower precedence operator, a higher precedence operator binds more tightly; but this means that it gathers up less of the expression around it: the implied parentheses are smaller. The higher precedence operator is also lower in the parse tree (assuming the parse tree is drawn in the usual Computer Science way: with its root at the top of the page!).

Interlude

That concludes what I have to say in explanation of the $ operator. A couple of clarifications and unusual cases follow, but my hope is that you now understand what the $ operator does and why. If not, I can only suggest that you go back and read through the material so far till you do! If understanding just won't come, please email me and perhaps together we can work out how to improve this tutorial.

The left-hand-side of the $ operator

Mostly we've been concerned with what occurs on the right-hand-side of the $ operator. As we've now established, it also gathers up everything on its left-hand-side (to the previous $ operator or the beginning of the expression), and this must yield a function.

Apart from simply naming a function (e.g. show in show $ x + y), the most common sort of expression that yields a function is a partial application (e.g. map toLower). Since function application has higher precedence than any operator (effectively it has precedence 10, and associates to the left), partial application will always be parsed as such. In other words, you never need to say map toLower $ s: it means the same as map toLower s.

Another sort of expression that yields a function is function composition with the . operator. This has precedence 9 (and is right associative), higher than any other operator, but lower than function application. So the $ operator can be used to group a function composition correctly: rather than (reverse . takeWhile ('/' /=) . reverse) fp you can write reverse . takeWhile ('/' /=) . reverse $ fp.

The $ operator in sections

As an operator, $ is a candidate for making sections. A section is a handy lump of syntactic sugar that effectively allows us to fix one argument of an operator. A couple of example sections: ('/' /=) is the function that is False when its Char argument is '/' and True otherwise; (+ n) is the function that adds n to its argument (obviously there must be a definition of n in scope).

In a right section, $ is redundant. The section (f $) is completely equivalent to (f), whether f is a simple expression or a complex one. All the $ could possibly do in this situation is to group everything to its left, and the brackets on their own do that just fine. (That's a handwavy sort of "proof": do let me know if you can find a counter example!)

In a left section, $ is more interesting. The section ($ x) is the function that takes a function argument and applies it to x. For example, map ($ "foobar") [take 3, drop 3] ["foo","bar"]. It's hard to think of a non-contrived use for this, but I did manage it. I wanted to test the performance of various different but - supposedly - equivalent definitions of a function. The functions are named in the list timeFunctions. Before the actual timing tests, I thought it would be wise to check that the functions indeed are equivalent (at least for the test arguments). The check for equivalence for a single argument x is performed by this function:

check x = all (r ==) rs
    where
      (r:rs) = map ($ x) timeFunctions

The entire timing harness is available as timing.hs. It demonstrates not only the $ section, but also a use of seq, and the awesome power of lazy evaluation: the simplest definition is (almost always) the fastest!

The $ operator as a function

Finally, any operator can be turned into a function by enclosing it in parentheses. Is there any use for the $ operator as a function: ($)? The Haskell report offers zipWith ($) fs xs: this takes a list of functions and a list of arguments, and returns the list which results from applying the first function to the first argument, the second function to the second argument and so on. It's hard to see that this has much to commend it over the equivalent list comprehension, [ f x | (f, x) <- zip fs xs ]. More seriously, I can't think of a non-contrived use for such an expression!

A more likely scenario is that you'd like to apply each of a list fs of functions to each of a list xs of arguments. This could be expressed by the list comprehension [ f x | f <- fs, x <- xs ]. It turns out that there is another way to express this, which does use the ($) function: the beautiful and mysterious liftM2 ($) fs xs. (Well, it's mysterious to me!) In fact, this expression is simply the definition of ap (which, along with liftM2, is exported by Control.Monad), so we can simply say ap fs xs. All three variants are included in wc0.hs: a bare-bones implementation of the Unix wc command, which counts the lines, words, and characters in the files named on its command line.

import Control.Monad (ap, liftM2)
import System.Environment (getArgs)

fs = [ length . lines, length . words, length ]

main = do
  as <- getArgs
  xs <- mapM readFile as
  let p = [ f x | f <- fs, x <- xs ]
      q = liftM2 ($) fs xs
      r = ap fs xs
  print p; print q; print r

Unfortunately, this version reports all the line counts, then all the word counts, then all the character counts (in other words, the xs vary more quickly than the fs). We'd prefer it to report all three counts for the first file, then the second file, and so on (with the fs varying more quickly). This is trivial to fix with the list comprehension: simply swap the order of the two generators. For the liftM2 version, we can swap the fs and the xs, and use the function flip ($), but it's starting to look a bit murky. There is no simple remedy for the ap version.

In summary, there are a few cases where we can make us of the function ($), but they are relatively rare. Do let me know if you come up with a good use for ($)!

Credits

This tutorial was written by Tim Goodwin.

© Copyright 2007, Tim Goodwin

Creative Commons License

This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.0 UK: England & Wales License.

Haskell code appearing in this document has been placed in the Public Domain: you can do absolutely anything you want with it.

All feedback is very welcome. Please email me if you have any comments whatsoever (good or bad!), questions, or suggestions for improvements.

Return to the Top

Problems? Comments? Questions? Contact us by email!