Reputation: 299
In Category theory, Functor concept is as the below:
https://ncatlab.org/nlab/show/functor
In Haskell, Functor
type can be expressed as:
fmap :: (a -> b) -> f a -> f b
https://hackage.haskell.org/package/base-4.14.0.0/docs/Data-Functor.html
and I could see the both really corresponds well.
However, once we actually try to implement this Functor concept down to a code, it seems impossible to define F
or fmap
as simple as the diagram shown above.
In fact, there is a famous article about Functor/Monad.
Functors, Applicatives, And Monads In Pictures
Here,
Simple enough. Lets extend this by saying that any value can be in a context. For now you can think of a context as a box that you can put a value in:
or
Here's what is happening behind the scenes when we write fmap (+3) (Just 2):
What I always feel about Functor
is the concept of Functor in category theory and concept of wrap&unwrap to/from "BOX" does not match well.
Quesion Point 1.
fmap :: (a -> b) -> f a -> f b
https://hackage.haskell.org/package/base-4.14.0.0/docs/Data-Functor.html
Where is the actual implementation of wrap&unwrap to/from "BOX" in Haskell?
Question Point 2.
Why the concept of Functor in category theory and concept of wrap&unwrap to/from "BOX" does not match well?
EDIT:
Even for IO
functor, during the composition process, f
is unwrapped:
// f is unwrapped in composition process
const compose = g => f => x => g(f(x));
const fmap = compose;
const print = a => () => console.log(a);
// safely no side-effect
const todo = fmap(print("bar"))(print("foo"));
//side effect
todo(undefined); // foo bar
// with pipleline operator (ES.next)
//
// const todo = print("foo")
// |> fmap(print("bar"))
// |> fmap(print("baz"));
// todo(undefined); // foo bar baz
Upvotes: 0
Views: 347
Reputation: 12505
Look at the arrows in the picture. There is no way to go from the functor level back to the non-functor level. You would need a function that goes from F(x)
to x
, but - as you can see - none is defined.
There are specific functors (like Maybe
) that offer the "unwrapping" functionality, but such feature is always an add-on, it's delivered on top of something being a functor. For example you might say: Maybe
is a functor, and also it has an interesting property: there's a partial function that maps Maybe X
to X, and which reverses pure
.
UPDATE (after the additional question appeared) The concepts of a box and a functor simply don't match. Also, as far as I know, no good metaphor for a functor (or a monad, or an applicative) has been found - and not for the lack of trying. It's not even surprising: most abstractions lack good metaphors, precisely because abstractions and metaphors are polar opposites (in some way).
Abstraction strips a concept to its core, leaving only the barest essentials. Metaphor on the other hand, extends a concept, widens the semantic space, suggests more meaning. When I say: "your eyes have the color of chocolate", I am abstracting a notion of a "color". But I also metaphorically associate the eyes and chocolate: I suggest that they have more in common than just the color: silky texture, sweetness, pleasure - all those concepts are present, although none were named. If I said "your eyes have the color of excrement" the abstraction used would be exactly the same - but the metaphorical meaning: very different. I would not say it even to a logician, even though a logician would technically understand that the sentence is not offensive.
When on auto-pilot, most humans think in metaphors, not abstractions. Care must be taken when explaining the latter in terms of the former, because the meanings will spill over. When you hear "a box", the autopilot in your head tells you that you can put stuff in and get stuff out. Functor is not like that. So the metaphor is misleading.
Functor embodies an abstraction of a... box or a wrapper, but one that allows us to work on their contents WITHOUT ever unwrapping it. This lack of unwrapping is exactly the thing which makes functors interesting: otherwise fmap
would just be a syntactic sugar for unwrapping, applying a function and wrapping the results up. Studying functors lets us understand how much is possible without unwrapping values - and, what's even better and more enlightening, it lets us understand what is impossible without unwrapping. The steps which lead up to applicatives, arrows and monads show us how to overcome some of the limitations by allowing additional operationsm but stukk without ever allowing unwrapping, because if we allowed unwrapping, the steps would make no sense (i.e. become trivial).
Upvotes: 5
Reputation: 233357
The ideas from category theory are so abstract that anyone who tries to provide an intuitive introduction runs the risk of simplifying concepts to the point where they may confuse people. As the author of an article series in this space, I can testify that it doesn't take much imprecise language before someone misunderstands the text.
I don't know the particular article, but I believe that it may exhibit the same trait. The wrap/unwrap metaphor fits a substantial subset of functors (e.g. Maybe
, []
, Either l
, etc.), but not all.
Famously, you're not supposed to unwrap IO
; that is by design. At that point, the wrap/unwrap metaphor falls apart. It's no longer valid in the face of IO
.
Indeed, the concepts don't match. I'd say that the wrap/unwrap metaphor may be useful as an introduction, but as always, there's a limit to how much you can stretch a metaphor.
How are the Functor
instances implemented? Most introductions to Haskell will show you how to write fmap
for Maybe
, []
, and a few other types. It can also be a good exercise to implement them yourself, if you get the chance.
GHC and its ecosystem is open source, so you can always look at the source code if you wonder how a particular instance is implemented.
Again, IO
is a big exception to the rule. As far as I understand it, its Functor
, Applicative
, Monad
, etc. instances aren't implemented in (Safe) Haskell, but rather in a small core of unsafe code (C or C++, I believe) that constitutes the core of the compiler and/or runtime environment. There's no (explicit, visible, safe) unwrapping going on with IO
. I think it's more helpful to think of IO
's Functor
instance as the structure-preserving map that it is.
For more details about the correspondence between category theory and Haskell, I recommend Bartosz Milewski's article series on the topic.
Upvotes: 5