Having read the book Learn you a Haskell For Great Good, and the very helpful wiki book article Haskell Category Theory which helped me overcome the common category mistake of confusing category objects with the programming objects, I still have the following question:
Why must fmap
map over every elements of a List?
I like that it does, I just want to understand how this is justified category theoretically. ( or perhaps it is easier to justify using HoTT? )
In Scala notation, List
is a functor that takes any type and maps that into an type from the set of all list types, eg it maps the type Int
to the type List[Int]
and it maps the functions on Int
eg
Int.successor: Int => Int
toFunctor[List].fmap(successor) : List[Int] => List[Int]
Int.toString: Int => String
toFunctor[List].fmap(toString): List[Int] => List[String]
Now every instance of List[X]
is a monoid with a empty
function (mempty
in Haskell) and combine
function (mappend
in Haskell). My guess is that one can use the fact that Lists are Monoids, to show that map
has to map all elements of a list. My feeling here is that if one adds the pure
function from Applicative, this gives us a list with just one element of some other type. e.g Applicative[List[Int]].pure(1) == List(1)
. Since map(succ)
on those elements gives us the singleton list with the next element, this covers all those subsets. Then I suppose the combine
function on all those singletons gives us all the other elements of the lists. Somehow I suppose that constrains the way map has to work.
Another suggestive argument is that map
has to map functions between lists. Since every element in a List[Int]
is of type Int, and if one maps to List[String]
one has to map every element of it, or one would not the right type.
So both of those arguments seem to point in the right direction. But I was wondering what was needed to get the rest of the way.
Counterexample?
Why is this not a counterexample map function?
def map[X,Y](f: X=>Y)(l: List[X]): List[Y] = l match {
case Nil => Nil
case head::tail=> List(f(head))
}
It seems to follow the rules
val l1 = List(3,2,1)
val l2 = List(2,10,100)
val plus2 = (x: Int) => x+ 2
val plus5 = (x: Int) => x+5
map(plus2)(List()) == List()
map(plus2)(l1) == List(5)
map(plus5)(l1) == List(8)
map(plus2 compose plus5)(l1) == List(10)
(map(plus2)_ compose map(plus5)_)(l1) == List(10)
Ahh. But it does not fit the id law.
def id[X](x: X): X = x
map(id[Int] _)(l1) == List(3)
id(l1) == List(3,2,1)
foo:: a -> a
not be the successor function?succ:: Int => Int
is not the identity function. succ 1 == 2 – Fant