Function Composition (. operator)
In Haskell, the . operator lets you compose multiple functions into a new function. (f . g) x means the same as f (g x) — it expresses "apply g first, then pass the result to f" as a single function. The $ operator is the function application operator. It has the lowest precedence and is right-associative, so you can use it in place of parentheses to make expressions easier to read. f $ g $ h x means the same as f (g (h x)). Combining these two operators lets you write declarative, "point-free style" code that is idiomatic Haskell.
Syntax
-- ----------------------------------------------- -- . operator (function composition) -- ----------------------------------------------- -- Type signature: (.) :: (b -> c) -> (a -> b) -> a -> c -- Composes two functions and returns a new function -- (f . g) x = f (g x) -- Example: compose negate and abs to create a function that -- takes the absolute value and then negates it -- negateAbs :: Int -> Int -- negateAbs = negate . abs -- negateAbs (-5) → -5 -- You can chain more than two functions (applied right to left) -- pipeline :: String -> String -- pipeline = reverse . map toUpper . filter (/= ' ') -- ----------------------------------------------- -- $ operator (function application / fewer parentheses) -- ----------------------------------------------- -- Type signature: ($) :: (a -> b) -> a -> b -- The result is the same as regular function application, -- but $ has the lowest precedence (0) and is right-associative -- f $ x = f x -- When parentheses become deeply nested, replacing them with $ improves readability -- With parentheses: putStrLn (show (negate (abs (-42)))) -- With $: putStrLn $ show $ negate $ abs (-42) -- ----------------------------------------------- -- Combining . and $ (point-free style) -- ----------------------------------------------- -- Defining a function without naming its argument is called point-free style -- Point-ful (x is explicit): -- process x = putStrLn (show (negate x)) -- -- Point-free (composed with . and applied with $): -- process = putStrLn . show . negate
Syntax Reference
| Operator / Pattern | Type Signature (summary) | Description |
|---|---|---|
(f . g) x | (b -> c) -> (a -> b) -> a -> c | Applies g first, then passes the result to f. Equivalent to f (g x). |
f . g . h | Chain composition of multiple functions | Functions are applied right to left. Equivalent to f (g (h x)). |
f $ x | (a -> b) -> a -> b | Function application operator. Its lowest precedence (0) lets you eliminate parentheses. |
f $ g $ h x | Right-associative chain of function application | Equivalent to f (g (h x)). |
f . g $ x | Compose then apply | Because . has higher precedence than $, f . g is composed first and then applied to x. |
| Point-free style | Definition without naming arguments | A style where process x = f (g x) is written as process = f . g. |
id | a -> a | The identity function. f . id = f and id . f = f hold, so it acts as the identity element for composition. |
Sample Code
sg_compose_basic.hs
-- sg_compose_basic.hs — demonstrates the basics of the . and $ operators
-- Uses character data from Steins;Gate to explore
-- function composition and point-free style
--
-- Compile and run:
-- ghc sg_compose_basic.hs -o sg_compose_basic && ./sg_compose_basic
module Main where
import Data.Char (toUpper, toLower)
-- -----------------------------------------------
-- . operator: basic function composition
-- -----------------------------------------------
-- Compose a function that converts a string to uppercase and then reverses it
-- Functions are applied right to left: map toUpper first, then reverse
reverseUpper :: String -> String
reverseUpper = reverse . map toUpper
-- reverseUpper "Hououin Kyouma" → "AMUOYK NIUOUOH"
-- Compose a function that doubles the length of a string
-- length gets the character count, then (* 2) doubles it
doubleLength :: String -> Int
doubleLength = (* 2) . length
-- doubleLength "Makise Kurisu" → 26
-- -----------------------------------------------
-- $ operator: reducing parentheses
-- -----------------------------------------------
-- Written with parentheses (tends to nest deeply)
withParens :: String -> String
withParens name = reverse (map toUpper (name ++ "!"))
-- Written with $ (reads naturally from left to right)
withDollar :: String -> String
withDollar name = reverse . map toUpper $ name ++ "!"
-- Combining . and $: first builds name ++ "!",
-- then passes it to the composed function reverse . map toUpper
-- -----------------------------------------------
-- Point-free style (omitting the argument)
-- -----------------------------------------------
-- Point-ful (argument name is explicit)
greetPointwise :: String -> String
greetPointwise name = "Hello, " ++ reverse (map toUpper name)
-- Point-free (composed with ., argument omitted)
-- Haskell's currying ensures this works correctly without the explicit argument
greetPointFree :: String -> String
greetPointFree = ("Hello, " ++) . reverse . map toUpper
-- -----------------------------------------------
-- Entry point
-- -----------------------------------------------
main :: IO ()
main = do
putStrLn "===== . operator: function composition ====="
-- Apply reverseUpper to each lab member's name
putStrLn $ "reverseUpper \"Hououin Kyouma\" = " ++ reverseUpper "Hououin Kyouma"
putStrLn $ "reverseUpper \"Makise Kurisu\" = " ++ reverseUpper "Makise Kurisu"
putStrLn $ "doubleLength \"Shiina Mayuri\" = " ++ show (doubleLength "Shiina Mayuri")
putStrLn $ "doubleLength \"Hashida Itaru\" = " ++ show (doubleLength "Hashida Itaru")
putStrLn ""
putStrLn "===== $ operator: reducing parentheses ====="
-- withParens and withDollar return the same result
putStrLn $ "withParens \"Urushibara Ruka\" = " ++ withParens "Urushibara Ruka"
putStrLn $ "withDollar \"Urushibara Ruka\" = " ++ withDollar "Urushibara Ruka"
putStrLn ""
putStrLn "===== Point-free style ====="
-- Point-ful and point-free return the same result
putStrLn $ "pointwise \"Hououin Kyouma\" = " ++ greetPointwise "Hououin Kyouma"
putStrLn $ "pointFree \"Hououin Kyouma\" = " ++ greetPointFree "Hououin Kyouma"
ghc sg_compose_basic.hs -o sg_compose_basic && ./sg_compose_basic [1 of 1] Compiling Main ( sg_compose_basic.hs, sg_compose_basic.o ) Linking sg_compose_basic ... ===== . operator: function composition ===== reverseUpper "Hououin Kyouma" = AMUOYK NIUOUOH reverseUpper "Makise Kurisu" = USIRUК ESIKAM doubleLength "Shiina Mayuri" = 26 doubleLength "Hashida Itaru" = 26 ===== $ operator: reducing parentheses ===== withParens "Urushibara Ruka" = !AKUR ARABIHS URU withDollar "Urushibara Ruka" = !AKUR ARABIHS URU ===== Point-free style ===== pointwise "Hououin Kyouma" = Hello, AMUOYK NIUOUOH pointFree "Hououin Kyouma" = Hello, AMUOYK NIUOUOH
sg_compose_pipeline.hs
-- sg_compose_pipeline.hs — demonstrates pipeline processing with function composition
-- Transforms and formats lab member data from Steins;Gate
-- using a pipeline built with . and $
--
-- Compile and run:
-- ghc sg_compose_pipeline.hs -o sg_compose_pipeline && ./sg_compose_pipeline
module Main where
import Data.Char (toUpper)
import Data.List (intercalate, sortBy)
import Data.Ord (comparing, Down(..))
-- -----------------------------------------------
-- Data type definition
-- -----------------------------------------------
-- Represents a single lab member
data LabMember = LabMember
{ memberNo :: Int -- Lab member number
, memberName :: String -- Name
, iq :: Int -- IQ
, isActive :: Bool -- Current participation status
} deriving (Show)
-- -----------------------------------------------
-- Sample data
-- -----------------------------------------------
-- List of lab members at the Future Gadget Laboratory
labMembers :: [LabMember]
labMembers =
[ LabMember { memberNo = 1, memberName = "Hououin Kyouma", iq = 170, isActive = True }
, LabMember { memberNo = 2, memberName = "Makise Kurisu", iq = 190, isActive = True }
, LabMember { memberNo = 3, memberName = "Shiina Mayuri", iq = 90, isActive = True }
, LabMember { memberNo = 4, memberName = "Hashida Itaru", iq = 168, isActive = True }
, LabMember { memberNo = 9, memberName = "Urushibara Ruka", iq = 110, isActive = False }
]
-- -----------------------------------------------
-- Individual transformation functions (pipeline pieces)
-- -----------------------------------------------
-- Filters to only active members
filterActive :: [LabMember] -> [LabMember]
filterActive = filter isActive
-- Sorts members by IQ in descending order
sortByIQ :: [LabMember] -> [LabMember]
sortByIQ = sortBy (comparing (Down . iq))
-- Formats a single member as a string
formatMember :: LabMember -> String
formatMember m =
"No." ++ pad3 (memberNo m)
++ " " ++ padRight 18 (memberName m)
++ " IQ: " ++ padLeft 3 (show (iq m))
where
-- Zero-pads the number to 3 digits
pad3 n
| n < 10 = "00" ++ show n
| n < 100 = "0" ++ show n
| otherwise = show n
-- Right-pads a string with spaces to the given width
padRight n s = s ++ replicate (max 0 (n - length s)) ' '
-- Left-pads a string with spaces to the given width
padLeft n s = replicate (max 0 (n - length s)) ' ' ++ s
-- -----------------------------------------------
-- Pipeline function composed with .
-- -----------------------------------------------
-- A pipeline that filters active members, sorts by IQ, and formats each one
-- Applied right to left: filterActive → sortByIQ → map formatMember
buildReport :: [LabMember] -> [String]
buildReport = map formatMember . sortByIQ . filterActive
-- -----------------------------------------------
-- Output with parentheses reduced using $
-- -----------------------------------------------
-- Assembles the full report string with a header
-- $ avoids deeply nested calls to putStrLn
printReport :: [LabMember] -> IO ()
printReport members = do
putStrLn "===== Active Lab Members (by IQ) ====="
-- $ lets you write unlines and buildReport without nesting
putStr $ unlines . buildReport $ members
putStrLn $ "Total: " ++ show (length (filterActive members)) ++ " members"
-- -----------------------------------------------
-- Entry point
-- -----------------------------------------------
main :: IO ()
main = do
printReport labMembers
putStrLn ""
putStrLn "===== All Lab Members (with status) ====="
-- Passes a composed function to mapM_ to print each line
mapM_ (putStrLn . formatWithStatus) labMembers
putStrLn ""
putStrLn "===== Names only (point-free) ====="
-- Composes map memberName and intercalate ", " with .
putStrLn . intercalate ", " . map memberName $ labMembers
where
-- Formats a member with their participation status
formatWithStatus m =
formatMember m ++ " Status: " ++ if isActive m then "Active" else "Inactive"
where
-- Locally redefines formatMember to reuse its helper functions
formatMember mm =
"No." ++ pad3 (memberNo mm)
++ " " ++ padRight 18 (memberName mm)
++ " IQ: " ++ padLeft 3 (show (iq mm))
pad3 n
| n < 10 = "00" ++ show n
| n < 100 = "0" ++ show n
| otherwise = show n
padRight n s = s ++ replicate (max 0 (n - length s)) ' '
padLeft n s = replicate (max 0 (n - length s)) ' ' ++ s
ghc sg_compose_pipeline.hs -o sg_compose_pipeline && ./sg_compose_pipeline [1 of 1] Compiling Main ( sg_compose_pipeline.hs, sg_compose_pipeline.o ) Linking sg_compose_pipeline ... ===== Active Lab Members (by IQ) ===== No.002 Makise Kurisu IQ: 190 No.001 Hououin Kyouma IQ: 170 No.004 Hashida Itaru IQ: 168 No.003 Shiina Mayuri IQ: 90 Total: 4 members ===== All Lab Members (with status) ===== No.001 Hououin Kyouma IQ: 170 Status: Active No.002 Makise Kurisu IQ: 190 Status: Active No.003 Shiina Mayuri IQ: 90 Status: Active No.004 Hashida Itaru IQ: 168 Status: Active No.009 Urushibara Ruka IQ: 110 Status: Inactive ===== Names only (point-free) ===== Hououin Kyouma, Makise Kurisu, Shiina Mayuri, Hashida Itaru, Urushibara Ruka
Overview
The . operator (function composition) in Haskell has type (.) :: (b -> c) -> (a -> b) -> a -> c. It composes two functions and returns a new function. (f . g) x means the same as f (g x), with functions applied from right to left. You can chain three or more functions with f . g . h to express a data transformation pipeline in a declarative style. The $ operator (function application) has type ($) :: (a -> b) -> a -> b. The result is identical to ordinary function application, but its lowest precedence (0) and right-associativity let you replace deeply nested parentheses — writing f $ g $ h x instead of f (g (h x)). Combining . and $ as in f . g $ x lets you express the natural flow of "compose functions first, then pass a value at the end." Point-free style — defining functions without naming their arguments — is achieved by composing with . and is an idiomatic Haskell way to express concise processing pipelines. For combining these operators with higher-order functions, see Higher-Order Functions (map / filter / foldr). For lambda expressions and partial application, see Basic Function Definitions.
If you find any errors or copyright issues, please contact us.