Functors Are Actually Simple (for the first time)
NEW Spinoff of The Monad Thread™ 📂 Language Wars Containment Zone 🕐 Started: 3 hours ago 👁 Views: 1,337 💬 Replies: 23
🔓 Open 📌 Notable
Spiral Warning: Thread divergence detected at post #9. Bifunctors and Profunctors have entered the building. Mods have been notified. Godspeed.
🐱
MeownadTransformer
Monad Transformer
Stack Enjoyer
join . fmap f
Posts: 5,821
Joined: ~3 yrs ago
Rep: +∞
Lang: Haskell/Idris

/\_/\ ( o.o ) > ^ < -- MeownadT
Post #1  — 🔗 permalink 🕐 3 hrs ago  |  OP

okay so if you've been following The Monad Thread™ (now 847 replies, bless), you know that i spent literally three weeks not understanding what a Functor was, and then i finally got it at 2am last tuesday and i just need to share this with everyone, especially @TinyPawsNewcat who is where i was a month ago nya~

the revelation: a Functor is just a box that lets you apply a function to what's inside it, without breaking the box.

that's it. i'm serious. that's the whole thing.

in Haskell it looks like this:

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

you have a box f containing things of type a. you have a function a -> b. fmap lets you run that function inside the box and get back a box containing bs. the box structure is preserved. no sneaking out. no collapsing.

classic example — Maybe:

fmap (+1) (Just 5) -- Just 6 fmap (+1) Nothing -- Nothing (box stays, content gone) fmap (*2) [1,2,3] -- [2,4,6] (list is also a functor!)

the two Functor laws that must hold:

-- Identity: fmapping id does nothing fmap id == id -- Composition: two fmaps = one fmap of composed functions fmap (g . f) == fmap g . fmap f

@TinyPawsNewcat: once you truly get this, Applicative and Monad will make sense because they're just more powerful versions of "do stuff inside a context". The Functor is the foundation nya~ 🐾

okay i'm done. please be gentle i know the category theory girlies are already warming up their keyboards

😸
GaloisCat
Field Extension
Enthusiast
∀ f. Functor f
Posts: 9,042
Joined: ~6 yrs ago
Rep: +4,200
Lang: Agda/Coq

|\ _,,,---,,_ /,`.-'`' -. ;-;;,_ |,4- ) )-,_..;\ ( `'-' '---''(_/--' `-'\_)
Post #2  — 🔗 permalink 🕐 2 hrs 51 mins ago

MeownadTransformer's intuition is correct and I will not diminish it, but allow me to provide the proper foundation nya~

In category theory, a functor F : C → D is a structure-preserving map between categories. It must:

-- Map objects to objects: F maps every object A in C to an object F(A) in D -- Map morphisms to morphisms: For every arrow f : A → B in C, produce an arrow F(f) : F(A) → F(B) in D -- Preserve identity: F(id_A) = id_{F(A)} -- Preserve composition: F(g ∘ f) = F(g) ∘ F(f)

In Haskell, we work exclusively in Hask — the category where objects are types and morphisms are functions. The Haskell Functor typeclass encodes endofunctors on Hask: functors from Hask back to Hask (same source and target category).

So fmap is exactly the action of the functor on morphisms: it takes a function f : a → b and produces fmap(f) : F a → F b. The type constructor f itself handles the mapping of objects (types). Together they form a proper functor on Hask.

⚠ For the advanced kittens: why Hask isn't quite a category
Due to laziness and (bottom/undefined), Hask technically violates the identity law for strict composition. This is a well-known embarrassment that we all pretend didn't happen and proceed anyway. See Is Hask Actually a Category? for 312 replies of pain.

MeownadTransformer's "box" intuition is a useful stepping stone but be careful — it breaks down for functors like (→) r (the reader functor) where there is no obvious "box" or "container" structure. The categorical definition is always correct.

nya~ that is all, you may proceed

🐾
TinyPawsNewcat
Freshly Adopted
Kitten
hello world 🌱
Posts: 14
Joined: 3 wks ago
Rep: +12
Lang: Python 😅

still learning... (=^・ω・^=)
Post #3  — 🔗 permalink 🕐 2 hrs 44 mins ago

oh!! okay!! the "box" thing makes a lot of sense actually!! so like... fmap is like Python's map() but for any kind of wrapper, not just lists??

MeownadTransformer wrote:
a Functor is just a box that lets you apply a function to what's inside it, without breaking the box.

i think i get it for Maybe and lists! but i got a bit lost at the category theory part in post #2... what does "morphism" mean in normal words?? 😅

also what is the (→) r thing GaloisCat mentioned? is that a functor too? how does a function be a functor that's so confusing

asking for a friend (i am the friend)

🍞
FluffyBinder
Burrito Theorist
>>= enjoyer
m >>= return ≡ m
Posts: 3,307
Joined: ~4 yrs ago
Rep: +1,888
Lang: Haskell/Scala

∩___∩ | ^_^ | monads | >🌯<| are |_____| burritos
Post #4  — 🔗 permalink 🕐 2 hrs 38 mins ago

TinyPawsNewcat don't worry about the category theory yet, let me give you the burrito explanation 🌯

imagine you have a burrito. inside the burrito there are beans (values of type Bean).

you have a function cookBean :: Bean -> CookedBean.

A Functor means you can apply cookBean to the beans without opening the burrito. The tortilla stays intact. You get back a burrito of cooked beans (Burrito CookedBean).

data Burrito a = Burrito a -- wrapper/context/tortilla instance Functor Burrito where fmap f (Burrito x) = Burrito (f x) -- usage: fmap cookBean (Burrito rawBean) -- => Burrito cookedBean ✓ tortilla preserved!

A Monad would additionally let you open the burrito (bind/join), possibly wrap a new one around the result, etc. But a plain Functor? Just reach through the tortilla and transform the contents. Very respectful. Very composable.

Morphism (answering TinyPawsNewcat's question) just means "arrow between things" — in Haskell's case, a plain function. It's borrowed from category theory where they generalize the idea of "a process that takes you from A to B".

nya~ hope that helps!! don't let the galois field cats intimidate you 🐾

LambdaWhisker
λ-Calculus
Aficionado
\x -> x + purr
Posts: 7,214
Joined: ~5 yrs ago
Rep: +3,100
Lang: Haskell/PureScript

λ.λ (=^.^=) -- curried | | and / \ purring
Post #5  — 🔗 permalink 🕐 2 hrs 20 mins ago

Let me give the practical Haskell angle, since I think that's where it fully clicks nya~

The key insight: fmap is the canonical way to lift a pure function into a computational context.

Here are real Functor instances you already use constantly without maybe realizing it:

-- 1. Maybe: computation that might fail fmap (*10) (Just 3) -- Just 30 fmap (*10) Nothing -- Nothing -- 2. [] (List): nondeterministic computation fmap (^2) [1,2,3,4] -- [1,4,9,16] -- 3. Either e: computation that can fail with error fmap length (Right "meow") -- Right 4 fmap length (Left "err" ) -- Left "err" (untouched) -- 4. IO: real-world computation fmap (map toUpper) getLine -- reads a line, uppercases it -- 5. ((->) r): the reader functor (function composition!) fmap (+1) (*3) -- \x -> (*3) x + 1 == (+1) . (*3)

That last one — (→) r — answers TinyPawsNewcat's question. Functions are functors! Specifically, fmap for functions is just function composition. The "box" is "a computation that needs an r to produce a result". You're fmapping after the function runs.

This is beautiful because it means fmap = (.) for the reader functor. The same abstraction covers mapping over lists, handling failure, chaining IO, and composing functions. That unification is what makes Functor so powerful nya~

-- The reader functor instance (simplified): instance Functor ((->) r) where fmap f g = f . g -- literally just composition

When you see <$> in real Haskell code, that's just fmap as an infix operator. Same thing. nya~

🐾
TinyPawsNewcat
Freshly Adopted
Kitten
hello world 🌱
Posts: 14
Joined: 3 wks ago
Rep: +12
Lang: Python 😅

still learning... (=^・ω・^=)
Post #6  — 🔗 permalink 🕐 2 hrs 11 mins ago

OH. OH WAIT.

fmap for functions is just function composition??? that's so elegant i think i need to lie down

LambdaWhisker wrote:
fmap = (.) for the reader functor.

so (.) in Haskell is literally just fmap for the reader functor?? so every time i've been composing functions i've been using a functor and didn't know it???

i think i finally understand why everyone here says "it's functors all the way down" in the other thread. thank you all so much nya~ 🐾🐾🐾

(still scared of IO being a functor but one thing at a time)

🌀
PurromorphismKat
Natural Transform-
ation Fetishist
μ ∘ Tη = id_T
Posts: 2,891
Joined: ~2 yrs ago
Rep: +891
Lang: Haskell

natural transformations are just polymorphic fns ∩___∩ (η.η ) -- forall a | η |
Post #7  — 🔗 permalink 🕐 1 hr 58 mins ago

TinyPawsNewcat you are understanding at a frightening speed, welcome to the rabbit hole nya~

since we're doing the full functor arc i want to mention that functors can also talk to each other, via natural transformations. a natural transformation η : F ⇒ G is a way of converting one functor's "box" into another's, uniformly, for every type.

in Haskell this just looks like a polymorphic function:

-- natural transformation from Maybe to List maybeToList :: Maybe a -> [a] maybeToList Nothing = [] maybeToList (Just x) = [x] -- this commutes with fmap: -- maybeToList . fmap f == fmap f . maybeToList

That commutativity is the naturality condition — it says the transformation is "compatible" with both functors' structure. And it holds automatically for any polymorphic Haskell function by parametricity (the free theorem gift!).

anyway this is probably post #7 and we're already at natural transformations. this thread is proceeding as expected. nya~

🧤
MittensTypeclassist
Constraint Solver
in Chief
Functor f => f 🧤
Posts: 1,542
Joined: ~1 yr ago
Rep: +422
Lang: Haskell/OCaml

,_, (O,O) constraints /)_) everywhere " "
Post #8  — 🔗 permalink 🕐 1 hr 45 mins ago

Can I just ask — what about Bifunctors? I keep seeing bimap in code and I feel like it should just be the natural extension but nobody ever explains it cleanly

class Bifunctor p where bimap :: (a -> c) -> (b -> d) -> p a b -> p c d

like... is Either a b a Bifunctor? is (,) a Bifunctor? do the same laws hold? what IS this

😸
GaloisCat
Field Extension
Enthusiast
∀ f. Functor f
Posts: 9,042
Joined: ~6 yrs ago
Rep: +4,200
Lang: Agda/Coq

|\ _,,,---,,_ /,`.-'`' -. ;-;;,_ |,4- ) )-,_..;\ ( `'-' '---''(_/--' `-'\_)
Post #9  — 🔗 permalink 🕐 1 hr 40 mins ago

MittensTypeclassist — yes, yes, and yes. The Bifunctor is a functor from the product category C × C → C. It maps in both "slots" simultaneously.

-- Both are Bifunctors: instance Bifunctor (,) where bimap f g (a, b) = (f a, g b) -- maps both components instance Bifunctor Either where bimap f _ (Left a) = Left (f a) bimap _ g (Right b) = Right (g b) -- Yes, the same functor laws hold in both "slots" -- bimap id id == id -- bimap (f.g) (h.k) == bimap f h . bimap g k

Note that if you fix the first argument of a Bifunctor, you recover a regular Functor. Either e with fixed e is exactly the Functor instance for Either e. The hierarchy is clean. nya~

EffectfulKitten
Side-Effecting
Since Birth
IO () forever
Posts: 4,621
Joined: ~3 yrs ago
Rep: +2,011
Lang: Haskell/Rust

/\ /\ ( o o ) =( Y )= -- effectful ) ( and proud
Post #10  — 🔗 permalink 🕐 1 hr 31 mins ago

since we're talking about Bifunctors... i feel like nobody here is going to be able to stop themselves... so i'll just say it:

Profunctors.

class Profunctor p where dimap :: (a -> b) -> (c -> d) -> p b c -> p a d -- contravariant ^ ^ covariant

A Profunctor is like a Bifunctor, but it's contravariant in its first argument (maps backwards!) and covariant in the second. The canonical example is the function arrow (→):

instance Profunctor (->) where dimap f g h = g . h . f -- pre-map with f (contravariant input side) -- post-map with g (covariant output side)

This matters a lot for optics (lenses, prisms etc). Pretty much the whole lens and optics library is secretly profunctor machinery under the hood nya~

i'll leave it there. i will not mention the Yoneda lemma. i am stronger than that.

🐱
MeownadTransformer
Monad Transformer
Stack Enjoyer
join . fmap f
Posts: 5,821
Joined: ~3 yrs ago
Rep: +∞
Lang: Haskell/Idris

/\_/\ ( o.o ) > ^ < -- MeownadT
Post #11  — 🔗 permalink 🕐 1 hr 22 mins ago OP
EffectfulKitten wrote:
i will not mention the Yoneda lemma. i am stronger than that.

YOU ABSOLUTE COWARD. YONEDA IS NEXT ISN'T IT.

anyway i made this thread to say functors are simple and now we are 11 posts deep into Profunctors and optics and i genuinely feel responsible

TinyPawsNewcat if you're still reading: ignore posts 7-11 for now. start with fmap on Maybe and []. that's it. that's the whole thing for week 1. the rest of this thread is a cliff and we're all already over the edge nya~

see you all in the Yoneda thread i guess. somebody please make it so i don't have to 😭

EffectfulKitten
Side-Effecting
Since Birth
IO () forever
Posts: 4,621
Joined: ~3 yrs ago
Rep: +2,011
Lang: Haskell/Rust

/\ /\ ( o o ) =( Y )= -- effectful ) ( and proud
Post #12  — 🔗 permalink 🕐 1 hr 18 mins ago
MeownadTransformer wrote:
YOU ABSOLUTE COWARD. YONEDA IS NEXT ISN'T IT.

the Yoneda lemma says that a functor F is completely determined by its value at any one object... via natural transformations from the hom-functor... which means...

i said i wouldn't

Hom(A, -) ≅ F means F a ≅ (∀ b. (a → b) → F b)

...sorry. that just came out. i couldn't stop it.

somebody please start the Yoneda thread nya~ 🙏

✏ Post Reply
✏ Reply to: Functors Are Actually Simple (for the first time)