Skip to content
AgileLogics
5 min read

Composition and the $ Operator

Prerequisites: Currying and partial application

Two small operators appear in almost every Haskell program.

$: application without parentheses

f $ x means f x, but $ has the lowest precedence of all operators, so everything to its right is evaluated first. It replaces a pair of parentheses that would extend to the end of the line:

main :: IO ()
main = do
  print (sum (map (* 2) (filter even [1 .. 10])))
  print $ sum $ map (* 2) $ filter even [1 .. 10]
60
60

Both lines are the same program. Read the second from right to left: keep the even numbers, double them, add them up, print.

.: function composition

(f . g) x means f (g x) — the dot combines two functions into a new one, just like ∘ in mathematics:

import Data.Char (toUpper)

shout :: String -> String
shout = map toUpper . reverse

countEvens :: [Int] -> Int
countEvens = length . filter even

main :: IO ()
main = do
  putStrLn (shout "hello")
  print (countEvens [1, 2, 3, 4, 6])
  print ((negate . abs) 7, (show . (* 2)) 21)
OLLEH
3
(-7,"42")

Composition builds a pipeline of functions that data flows through from right to left. Combined with $, a common style is:

main :: IO ()
main = print . sum . map (* 2) . filter even $ [1 .. 10]
60

& for left-to-right

If you prefer reading left to right, Data.Function provides & (reverse application):

import Data.Function ((&))

main :: IO ()
main = [1 .. 10] & filter even & map (* 2) & sum & print
60

Common mistakes

  • print . sum [1, 2] fails: composition joins functions, but sum [1, 2] is already a number. Write print . sum $ [1, 2] or print (sum [1, 2]).
  • Overusing point-free style. f = (. g) . (+) is clever but unreadable; name the arguments when that's clearer.