What Is a Functor?
A Functor is a type class for things that can be mapped over while preserving their shape: class Functor f where fmap :: (a -> b) -> f a -> f b. Given a function from a to b and a value of type f a -- a Maybe a, a [a], an Either e a, or any other container-like structure -- fmap applies the function to the value(s) inside without changing the surrounding structure itself.
Cricket analogy: Applying fmap to a Maybe Int is like a physio applying the same fitness-rating formula to a player only if that player is actually selected in the squad -- if the slot is empty (Nothing/unselected), there's nobody to rate and nothing happens.
The Maybe, List, and Either Functors
For Maybe, fmap f (Just x) = Just (f x) and fmap f Nothing = Nothing, so mapping over an absent value is a safe no-op rather than a crash. For lists, fmap is just map: fmap (+1) [1,2,3] = [2,3,4]. For Either e a, fmap only touches the Right case, leaving Left errors untouched -- this is why Either is commonly used to thread an error value through a computation while still allowing fmap to operate on success values.
Cricket analogy: fmap toUpper over Just "kohli" producing Just "KOHLI" while leaving Nothing untouched is like a scoreboard operator capitalizing a batter's name only when a batter is actually at the crease -- an empty crease stays empty.
import Data.Char (toUpper)
shout :: Maybe String -> Maybe String
shout = fmap (map toUpper)
main :: IO ()
main = do
print (shout (Just "hello")) -- Just "HELLO"
print (shout Nothing) -- Nothing
print (fmap (*2) [1,2,3]) -- [2,4,6]
print (fmap length (Right "hi" :: Either String String)) -- Right 2<$> is the infix synonym for fmap, so fmap (+1) (Just 5) and (+1) <$> Just 5 are identical. Seeing <$> in real Haskell code is far more common than seeing the word fmap spelled out, especially in applicative-style pipelines.
Functor Laws
Every valid Functor instance must obey two laws: the identity law, fmap id = id, meaning mapping the identity function over a structure changes nothing, and the composition law, fmap (f . g) = fmap f . fmap g, meaning mapping a composed function is the same as mapping each function in sequence. These laws guarantee that fmap only ever changes the values inside a structure, never its shape -- a fmap over a three-element list always yields a three-element list.
Cricket analogy: The functor law fmap id = id is like re-announcing a batting lineup with no substitutions -- reading out the same eleven names in the same order changes nothing about the team sheet itself.
Functors for Custom Types
Writing a Functor instance for a custom type follows the same pattern: for data Box a = Box a, the instance is instance Functor Box where fmap f (Box x) = Box (f x). For types with multiple constructors or fields, GHC can often derive the instance automatically with {-# LANGUAGE DeriveFunctor #-} data Tree a = Leaf | Node (Tree a) a (Tree a) deriving Functor, saving you from hand-writing the recursive traversal.
Cricket analogy: Writing a Functor instance for a custom data Innings a = Innings a type is like defining, once, how to update the score inside an innings record regardless of whether it holds runs, wickets, or overs -- the wrapper (Innings) stays fixed.
fmap can only transform values in place -- it can never change the number of elements, remove a Nothing, or reorder a structure, because doing so would break the functor laws. If you need to filter, flatten, or change shape based on the contained value, you need a different tool such as Monad's >>= or a fold, not fmap.
- Functor is a type class providing fmap :: (a -> b) -> f a -> f b.
- fmap applies a function inside a structure without changing its shape.
- Maybe, [], and Either are all Functor instances with distinct fmap behavior.
- <$> is the infix operator equivalent to fmap and appears constantly in real code.
- The identity and composition laws guarantee fmap never alters structure, only contents.
- DeriveFunctor can automatically generate instances for many custom data types.
- fmap cannot filter, flatten, or resize a structure -- only transform values in place.
Practice what you learned
1. What is the type signature of fmap?
2. What does fmap do to a Nothing value?
3. What is the infix operator equivalent to fmap?
4. Which law states that fmap id = id?
5. What GHC extension lets you automatically derive a Functor instance?
Was this page helpful?
You May Also Like
Higher-Order Functions in Haskell
Learn how Haskell treats functions as first-class values, and how map, filter, foldr, currying, and composition let you build programs out of small, reusable pieces.
Applicatives Explained
Learn how the Applicative type class extends Functor with pure and <*>, letting you combine several independent effectful values -- Maybe validations, list combinations, or IO actions -- with a plain function.
Algebraic Data Types
How Haskell builds custom types from sum types and product types using data declarations, pattern matching, records, and recursion.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics