smooth_writing
smooth_writing

Reputation: 299

Haskell functor implementation dealing with "BOX"?

In Category theory, Functor concept is as the below:

https://ncatlab.org/nlab/show/functor

enter image description here

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:

enter image description here

or

Here's what is happening behind the scenes when we write fmap (+3) (Just 2):

enter image description here

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

Answers (2)

fdreger
fdreger

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

Mark Seemann
Mark Seemann

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

Related Questions