Consider the following wrapper:
newtype F a = Wrap { unwrap :: Int }
I want to disprove (as an exercise to wrap my head around this interesting post) that there’s a legitimate Functor F
instance which allows us to apply functions of Int -> Int
type to the actual contents and to ~ignore~ all other functions (i. e. fmap nonIntInt = id
).
I believe this should be done with a free theorem for fmap
(which I read here):
for given f
, g
, h
and k
, such that g . f = k . h
: $map g . fmap f = fmap k . $map h
, where $map
is the natural map for the given constructor.
What defines a natural map? Am I right to assume that it is a simple flip const
for F
?
As far as I get it: $map f
is what we denote as Ff
in category theory. Thus, in a categorical sense, we simply want something among the lines of the following diagram to commute:
Yet, I do not know what to put instead of ???
s (that is, what functor do we apply to get such a diagram and how do we denote this almost-fmap
?).
So, what is a natural map in general, and for F
? What is the proper diagram for fmap
's free theorem?
Where am I going with this?
Consider:
f = const 42
g = id
h = const ()
k () = 42
It is easy to see that f . g
is h . k
. And yet, the non-existant fmap
will execute only f
, not k
, giving different results. If my intuition about the naturality is correct, such a proof would work. That's what I am trying to figure out.
@leftaroundabout proposed a simpler piece of proof: fmap show . fmap (+1)
alters the contents, unlike fmap $ show . (+1)
. It is a nice piece of proof, and yet I would still like to work with free theorems as an exercise.
So we are entertaining a function
m :: forall a b . (a->b) -> F a -> F b
such that (among other things)There are two somewhat related questions here.
fmap
do this?The answer to both questions is "no".
A well-behaved
fmap
can't do this becausefmap
has to obey the axioms ofFunctor
. Whether our environment is parametric or not is irrelevant. The axiom ofFunctor
says that for all functionsa
andb
,fmap (a . b) = fmap a . fmap b
must hold, and this fails fora = show
andb = (1 +)
. Som
cannot be a well-behavedfmap
.A parametric function can't do this because that is what the parametricity theorem says. When viewing types as relations between terms, related functions take related arguments to related results. It is easy to see that
m
fails parametricity, but it is slightly easier to look atm': forall a b. (a -> b) -> (Int -> Int)
(the two can be trivially converted to each other).(1 +)
is related toshow
becausem'
is polymorphic in its argument, so different values of the argument can be related by any relation. Functions are relations, and there exists a function that sends(1 +)
toshow
. However, the result type ofm'
has no type variables, so it corresponds to the constant relation (its values are only related to themselves). Since every value includingm'
is related to itself, it follows that all parametric functionsm :: forall a b. (a -> b) -> (Int -> Int)
must obeym f = m g
, i.e. they must ignore their first argument. Which is intuitively obvious since there is nothing to apply it to.One can in fact deduce the first statement from the second by observing that a well-behaved
fmap
must be parametric. So even if the language allows non-parametricity,fmap
cannot make any non-trivial use of it.