correct • elegant • free

△ The $ Operator △

◅ The $ Rule revisited

Credits ▻

Esoteric uses for the $ operator

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
      (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. I can see no simple remedy for the ap version, except to patch up the output afterwards. Here's wc1.hs if you're feeling brave!

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 ($)!

△ The $ Operator △

◅ The $ Rule revisited

Credits ▻