Why say the design of functor in Haskell is terrible for NEWBIE

What is Functor ?

The “functor” I want to discuss today is not the concept in category theory, but a core feature and language character in Haskell instead. In fact, the design of functor in Haskell is really dated from the concept in mathematics. First let us review the details of functor definition:

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

In this code, the type signature (a -> b) means a function that applied the value which type is “a”, then return other value with the type “b”. The “f” here declare a functorial strcuture (Notice! The f here is not a function which just like (a->b) ). Therefore the “f a” not means given a agrument “a” to f, but express that putting the type “a” object into the strcuture “f”.

the “f” make confused

So far everything is OK for those Haskell beginners. We stupid humans (yeah, sure, include me) are all jump for joy that we have understand a new important feature in Haskell. And then we follow some instance implementations, for example, the list.

1
2
3
instance Functor [] where
fmap f [] = []
fmap f (x:xs) = (f x)++(fmap f xs)

Well..wait, wait. What is the “f” here mean? Does it means another functorial structure? Of course not, because we can get the type information that the “f” is just the “(a -> b)” while “[]” is the true “f” corresponding the functor defination.

Perhaps someone says this different is not so serious that we only need to be careful when we check the type signature of those functor implementations. OK, I will give you a example which reveal mix them together may cause a huge confusion to we haskell newbies.

1
2
3
4
5
6
Prelude> let rmp = const 'p'
Prelude> :t rmp
rmp :: b -> Char
Prelude> let lms = [Just "123", Nothing, Just "abc"]
Prelude> :t lms
lms :: [Maybe [Char]]

Everything is going well, right? Then let’s rock.

1
2
Prelude> fmap rmp lms
"ppp"

That’s easy. Just replace each element of lms to ‘p’ to create a Chat list. Therefore we get “ppp”.

More complex:

1
2
3
4
5
6
7
8
Prelude> (fmap . fmap) rmp lms
[Just 'p',Nothing,Just 'p']
Prelude> :t (fmap . fmap) rmp lms
(fmap . fmap) rmp lms :: [Maybe Char]
Prelude> :t (fmap. fmap)
(fmap. fmap) :: (Functor f, Functor f1) => (a -> b) -> f (f1 a) -> f (f1 b)
Prelude>
Prelude>

What happened here? Why add addiontal fmap layer cause the Maybe type exist? What does the f (f1 a) mean in the (fmap . fmap) ? Does it means two function nested called? In fact the compiler has remind you that both f and f1 are functors. But many people, as the Haskell NEWBIEs, may regard the f (f1 a) as two function calling: first calcuate the f1 a, then apply the answer to f to get the last result by their prior knowledge.

In fact, this example came from a lastest Haskell Book( This book is very well that cover a lot of features and changes of lastest GHC ). To be honest, I am so stupid that get stucked here for a few days until I rewrite the functor definition in my own way. The f (f1 a) confused me, and I take a period time to realize that this means “nested structure” rather than “nested function”.

Functor or Function ?

I’m not sure if the functor and function is really the same thing at a high level abstraction on category theory. Not everyone is the well-armed category theory researcher. However, if these Haskell researchers tell their followers that “functor is somehow a structure blablabla…” which means they make a decision to keep these two things different, it’s better not to make the meaning of f different.

A personal modification for Functor

After I was stucked by this example for a while, I reread the related section of the book and rewrite the Functor to make the understanding more easily:

1
2
3
4
5
6
class Structor s where
smap :: (a -> b) -> s a -> s b

instance Structor [] where

smap f [] = []
smap f (x:xs) = (f x)++(fmap f xs)

Then retest the example, we get the type information:

1
2
3
4
5
6
Prelude> (smap . smap) rmp lms
[Just 'p',Nothing,Just 'p']
Prelude> :t (smap . smap) rmp lms
(smap . smap) rmp lms :: [Maybe Char]
Prelude> :t (smap. smap)
(smap. smap) :: (Functor s, Functor s1) => (a -> b) -> s (s1 a) -> s (s1 b)

I do nothing other than rename the “Functor” to “Structor”, “fmap” to “smap”, “f” to “s” (s means structure). Now I will be never confused by the mix of s and f. What’s more, when I found the s (s1 a) I realized that it just means nested structure immediately.

Conclusion

The problem I talked here is tiny (just a choise of name!). Bringing into correspondence with the concept on math and the related feature in Haskell sometimes is not necessery. They make obstacle for understanding to these beginners instead. Expert haskell programmers may neglect this issue because they have understood these concepts clearly, just the same situation as Monad. Do not make any other difficulties for those know-nothing but passionate folk.

tech