Functors

Course 2 · Ch 1
Functors
"A box you can map over" — the first step of the three-chapter arc that ends in Chapter 3's own monad reveal

haskell1-8 closed Course 1 with typeclasses. This chapter opens Course 2 with the first of three — Functor, then Applicative, then Monad — building toward the single biggest payoff in this entire track.

The Functor Typeclass

class Functor f where fmap :: (a -> b) -> f a -> f b

A real typeclass, same shape as haskell1-8's own Eq/Ord/Show — but with a genuine new wrinkle: f here isn't a concrete type, it's a type constructor — something that itself takes a type parameter, like Maybe or []. Functor is a capability for "things that hold a type parameter," not for ordinary types directly.

"A Box You Can Map Over" — The Core Intuition

fmap (+1) (Just 5) -- Just 6 fmap (+1) [1,2,3] -- [2,3,4] fmap (+1) Nothing -- Nothing — the function is simply never applied, safely

Maybe, [], and other "container-like" types let a function be applied to whatever's inside, without unwrapping and rewrapping by hand. This is genuinely one step further than java2-1's/csharp2-1's own generics — a generic type stores a T; a Functor additionally supplies a uniform way to transform whatever it's holding, without needing to know anything about the container itself.

fmap vs. map — A Real, Honest Naming Wrinkle

map is Haskell's own older, list-specific function, predating the generalized Functor typeclass. fmap is the generalized version, working for any Functor, not just lists. A real, slightly awkward wart from decades of language evolution: map still exists separately, mostly for historical and beginner-friendliness reasons, and newcomers often reasonably ask why there are two. Worth naming honestly rather than glossing over.

The <$> Operator — fmap's Infix Alias

(+1) <$> Just 5 -- identical to fmap (+1) (Just 5) — just nicer to read

<$> is fmap's infix alias, seen constantly in real Haskell code from here on — worth introducing now rather than later.

Writing a Functor Instance for Your Own Type

-- haskell1-6's own Tree a, now made Functor-capable: instance Functor Tree where fmap _ Leaf = Leaf fmap f (Node l v r) = Node (fmap f l) (f v) (fmap f r)

Functor isn't limited to Maybe and [] — any appropriately-shaped type can genuinely participate. This instance reuses haskell1-6's own Tree a directly, applying f to every value while leaving the tree's own shape untouched.

The Functor Laws

fmap id = id fmap (f . g) = fmap f . fmap g

A real, honest limitation: these two laws — mapping id changes nothing, and mapping a composed function equals composing the mapped functions — are not enforced by the compiler at all. They're purely a convention instance authors are expected to uphold, the same honest-acknowledgment spirit as Course 1's own record-syntax field-collision wart.

AspectJava/C# generics (java2-1, csharp2-1)Haskell Functor
What it providesa container that holds a Ta container PLUS a uniform way to transform what's inside
Requires knowing the container's internalsoften, to transform contentsnever — fmap works the same regardless
Compiler-enforced correctnesstype-safety onlytype-safety only — the Functor laws are unenforced
Reach for <$> in real code
<$> is the idiomatic form seen constantly in real Haskell — fmap itself is more useful for explanation and teaching contexts, exactly the balance this chapter uses.
The Functor laws aren't checked — a broken instance still compiles
A lawless Functor instance (one that doesn't actually satisfy fmap id = id) compiles and runs without any warning at all, and can produce genuinely surprising, wrong behavior anywhere code assumes the laws hold — including, later, inside do-notation. There is no compiler safety net here.

Coding Challenges

Challenge 1

Use fmap to double every value inside a Just 21, a Nothing, and a list [1,2,3,4], printing all three results.

📄 View solution
Challenge 2

Using haskell1-6's own Tree a data type, write a Functor instance for it, build a small sample tree, apply fmap (*10) to it, and print the result.

📄 View solution
Challenge 3

Write a short comment explaining why f in "class Functor f where" must be a type constructor (like Maybe) rather than a concrete type (like Int), tying your answer to what fmap's own type signature requires.

📄 View solution

Chapter 1 Quick Reference

  • Functor's f is a type constructor, not a concrete type — a genuine new wrinkle beyond ordinary typeclasses
  • fmap applies a function to whatever's inside a container without unwrapping/rewrapping by hand
  • map is Haskell's older, list-only function; fmap is the generalized version for any Functor — a real historical wart, honestly named
  • <$> is fmap's infix alias, the idiomatic form in real code
  • Any appropriately-shaped type can get a Functor instance, including haskell1-6's own Tree a
  • The Functor laws (fmap id = id, etc.) are pure convention — never compiler-enforced
  • Next chapter: Applicatives — combining independent computations, the bridge to Chapter 3's own monad reveal