Awesome question. I know of no source on the Internet that would explain this accessibly. I am not
sure I can explain it accessibly either. But it is, in essence, not even simple — it is trivial.
So brace yourself.
Disclaimer
I am not a guru, so lend awhile for the elders to destroy this answer before believing anything I say.
Theory
This is my favourite picture from Mac Lane's tome.
It is pointless to explain in words. You need to stare at it for a while and eventually see how it
tells you something about the example I presently provide. You can also read some
explanations on the Internet.
Practice
First we will define some types. They are in essence the same as the usual [a]
, but Stream
is
always infinite, and Vector
has exactly some prescribed length.
data Stream a = a :... Stream a
instance Functor Stream where
fmap f (x :... xs) = f x :... fmap f xs
data Vector (n :: Nat) a = a ::: Vector (n - 1) a | End deriving (Show, Eq)
infixr 4 :::
instance Functor (Vector n) where
fmap f (x ::: xs) = f x ::: fmap f xs
fmap f End = End
Now we will create a family of functions take_
that work in the same way as the Prelude.take
,
but only as our more refined types allow. Particularly, we will not be providing a run-time value
of n
, but a compile time type level annotation n
.
class Take (n :: Nat) where take_ :: Stream a -> Vector n a
instance Take 0 where take_ _ = End
instance {-# overlappable #-} ((m - 1) ~ n, Take n) => Take m
where take_ (x :... xs) = x ::: take_ @n xs
Let us also have some example values we can use to check if anything at all works. Here I define
the stream of natural numbers from 0 to infinity:
n :: Stream Integer
n = n_i 0
n_i :: Integer -> Stream Integer
n_i x = x :... n_i (x + 1)
We could not possibly observe an infinite stream, but with the take_
function we have we can
take a look at an arbitrary prefix thereof. See:
λ :set -XDataKinds
λ :set -XTypeApplications
λ take_ @3 n
0 ::: (1 ::: (2 ::: End))
It really is a natural transformation. Compare this example to Mac Lane's diagram:
λ take_ @10 . fmap (^2) $ n
0 ::: (1 ::: (4 ::: (9 ::: (16 ::: (25 ::: (36 ::: (49 ::: (64 ::: (81 ::: End)))))))))
λ fmap (^2) . take_ @10 $ n
0 ::: (1 ::: (4 ::: (9 ::: (16 ::: (25 ::: (36 ::: (49 ::: (64 ::: (81 ::: End)))))))))
Can you see how (^2)
is f
, fmap (^2)
is either S f
or T f
, and take_
is τ
?
Theory, revisited
As the elders pointed out in their answers, in Haskell, thanks to the awesome parametric
polymorphism we enjoy, you get some guarantees for free. Specifically, any parametrically
polymorphic transformation mapping between two lawful functors is natural. It is easy to believe,
but involved to prove. Here is one approach to this topic.
Notice also that a function that is not parametrically polymorphic — be it a monomorphic function
or a class method — may be a transformation that is not natural. Example:
class Break (n :: Nat) a where break_ :: Stream a -> Vector n a
instance Break 0 Integer where break_ _ = End
instance {-# overlappable #-} ((m - 1) ~ n, Break n Integer) => Break m Integer
where break_ (x :... xs) = if even x then x ::: break_ @n xs else (x + 1) ::: break_ @n xs
It really does break the law. Compare with the previous example:
λ break_ @10 . fmap (^2) $ n
0 ::: (2 ::: (4 ::: (10 ::: (16 ::: (26 ::: (36 ::: (50 ::: (64 ::: (82 ::: End)))))))))
λ fmap (^2) . break_ @10 $ n
0 ::: (4 ::: (4 ::: (16 ::: (16 ::: (36 ::: (36 ::: (64 ::: (64 ::: (100 ::: End)))))))))
The types of take_
and break_
have distinct constraints, and that is your telltale:
λ :t take_
take_ :: Take n => Stream a -> Vector n a
λ :t break_
break_ :: Break n a => Stream a -> Vector n a
— See that a
in break_
's constraint? That means the free theorem does not hold, and break_
may not be natural.
IO a
toMaybe a
is not possible? – Malapert