100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Functors Explained

Understand Haskell's Functor type class -- how fmap maps a function over a structure like Maybe, a list, or Either while preserving its shape, and the laws that guarantee it behaves predictably.

Functional AbstractionsIntermediate9 min readJul 10, 2026
Analogies

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.

haskell
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

Was this page helpful?

Topics covered

#Programming#HaskellStudyNotes#FunctorsExplained#Functors#Explained#Functor#Maybe#StudyNotes#SkillVeris#ExamPrep