Function composition and $ operator in Haskell
Today I was trying to understand how function application, composition and $ work in Haskell. Consider two following lines of code:
take 3 (reverse (filter even [1..10]))
take 3 . reverse . filter even $ [1..10]
These are equivalent and both produce a list [10, 8, 6]
. First version is
based on grouping function calls with parentheses and this is pretty
straightforward. My problem was the second version which is based on function
composition and a special function application operator, denoted as .
(dot)
and $
respectively. This form is often used to write functions in a pointfree
style1. I was trying
to work out in what order function calls are executed and why do I have to use $
operator. Here’s what I came up with.
The function composition (dot) operator is defined as:
(.) :: (b -> c) -> (a -> b) -> a -> c
. g) x = f (g x) (f
This operator has priority of 9 and is right-associative2. This means that a . b . c . d
is the same as (a . (b . (c . d)))
. The $
operator is defined
as:
($) :: (a -> b) -> a -> b
$ x = f x f
This operator simply applies a function to a given parameter. In contrast to
standard function application, which has highest possible priority of 10 and is
left-associative, the $ operator has priority of 0 and is right-associative
(that second property doesn’t matter in my example). Such a low priority means
that all other operators on both sides of $
will be evaluated before applying
the $
. So the call
take 3 . reverse . filter even $ [1..10]
will first evaluate take 3 . reverse . filter even
constructing a partially
applied function that becomes fully applied when it receives one more
parameter. The missing parameter is the list on the right side of $
. By
definition of dot operator, this call is therefore equivalent to take 3 (reverse (filter even [1..10]))
. That’s what we expected.
Why do we need the $
operator? If it wasn’t there then the function call
filter even [1..10]
would evaluate first - remember that function application
has priority of 10, while function composition has a lower priority of 9. This
would lead to take 3 . reverse . [2,4,6,8,10]
, but a list cannot be composed
with a function. The dot operator expects it’s second argument to be a function
of one argument, not a list, and that’s the reason we need $
- to allow
function composition to evaluate first.