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.

1 Answer
1

This relies on a theoretical result called "parametricity", first defined by Reynolds and then developed by Wadler (among others). Perhaps the most famous paper on this topic is "Theorems for free!" by Wadler.

The key idea is that from the polymorphic type of a function only, we can get some information about the semantics of the function. For instance:

foo :: a -> a

From this type alone, we can see that, if foo terminates, it is the identity function. Intuitively, foo can not distinguish between different as since in Haskell we do not have e.g. Java's instanceof which can inspect the actual runtime type. Similarly,

bar :: a -> b -> a

must return the first argument. And baz :: a -> a -> a must return either the first or the second. And quz :: a -> (a -> a) -> a must apply some number of times the function to the first argument. You probably get the idea now.

The general property which can be inferred from a type is quite complex, but luckily it can be computed mechanically. In category-theory, this is related to the notion of natural transformation.

could foo:: a -> a not be the successor function? succ:: Int => Int is not the identity function. succ 1 == 2
– Henry StoryApr 19 '16 at 11:45

4

@HenryStory : succ has the type Int -> Int, which is not the same as a -> a, that is, forall a. a -> a. The type of succ is more specific than a -> a, and map succ is well-typed, but according to maps type, map must work for all such functions, and thus cannot assume anything about them.
– opqdonutApr 19 '16 at 12:13

1

@HenryStory If Haskell allowed existention types, we could write succ :: exists a. a -> a. However, parametricity applies to types such as forall a. a -> a, i.e. functions that have to work on "all" arguments of the unknown type a.
– chiApr 19 '16 at 12:40

2

@HenryStory I'd would neglect HoTT for this. HoTT is beautiful, but it's about dependent types (and higher homotopies), which Haskell lacks. I don't believe HoTT can help here (but I'd love to be proved wrong on this...)
– chiApr 19 '16 at 12:42

1

Put another way, don't confuse (the syntactically illegal) forall a . succ :: a -> a (which say, "Give me a type, and I can provide a definition of such for that type) with succ :: forall a . a -> a, which says "Give me a definition of succ, and I can apply that definition to a value of any type."
– chepnerApr 19 '16 at 12:44