4

Is there any general functor (not limited to endofunctor) usage in programming?

I understand the reason endofunctor employed is to make the structure simple like monoid or monad.

I also understand ultimately, all the value is settled down to a category of a programming language (such as Hask), but what I'm talking about here is endofunctor between the same category of Strings, Numbers, Booleans, or Functions.

Related questions:

Are all Haskell functors endofunctors?

Differences between functors and endofunctors

smooth_writing
  • 299
  • 1
  • 6
  • 4
    What does "same category of Strings, Numbers, Booleans, or Functions" mean? – chepner Aug 24 '20 at 13:11
  • 5
    The answer at "Are all Haskell functors endofunctors?" boils down to "no", with some examples given. Can you explain why those examples do not also answer this question? – Daniel Wagner Aug 24 '20 at 14:02
  • @chepner The types of Strings, Numbers, Booleans as endomorphism/endofunctor – smooth_writing Aug 24 '20 at 20:43
  • 2
    Those are types with kind `Type`. A functor is something with kind `Type -> Type`. Are you looking to create a category that has individual `String` value as objects? It's not clear what the morphisms in such a category would be. – chepner Aug 24 '20 at 20:48
  • @chepner You appeared to deny what I'm explaining, but in https://stackoverflow.com/a/3273484/13685390 there is a mention > But it's also reasonable to talk about functors *between* subsets of **Hask**. For instance, consider a functor that sends types `Maybe a` to `[a]`. < This seems not to deny my mention. Does what you are saying here have a consistency of that answer? – smooth_writing Aug 25 '20 at 01:12
  • @chepner An endofunctor is something with kind someType -> someType, in other words, `Strings` -> `Strings`, what's wrong about that?? – smooth_writing Aug 25 '20 at 01:27
  • @chepner I guess the morphisms could be all string operations (any function mapping one string to another), and we could for example build a functor that maps all strings and their morphisms into strings that begin with a certain prefix. I guess that's no endofunctor though? Maybe instead map all strings and their morphisms into the category of reversed strings. – Bergi Aug 25 '20 at 11:29
  • @smooth_writing That is not quite what a functor is. I have seen this misconception before. `String` is *not* a category. A functor maps between categories, not types. In the category of Haskell types and Haskell functions, Hask, the objects are Haskell types. The arrows in this category are Haskell functions. What you are describing is just an arrow in this category, not a functor. In the answer that you linked, the fact that the function in question is polymorphic is absolutely crucial to it being a functor (in the context of Hask). – David Young Aug 25 '20 at 18:00
  • @David Nah, `String` is a category. Well, this is not a topic to discuss Category Theory basic, and if you can't agree, maybe a good idea to ask here or Mathoverflow. This is a typical misconception in programming community. – smooth_writing Aug 26 '20 at 21:20
  • @David in fact, any arrow in category theory is a functor, if you don't understand what I'm saying, just ask anywhere for the own topic. – smooth_writing Aug 26 '20 at 21:23

2 Answers2

5

First, yes.

For example, we all know that a monoid can be defined to be a single-object category with

  • arrows to be elements
  • the single object has no meaning
  • composition to be operator ((<>) in Haskell)
  • id arrow to be the identity (mempty in Haskell).

And a homomorphism between two monoids becomes a functor between two categories in this sense.

Now, say, type A and B are both monoids; A functor between them is just a homomorphic function f :: A -> B that maps each A to B, preserving the composition.

But, wait, f :: A -> B is not even a Functor (note that I use the monospaced typeface here)!

No, it is not a Functor in Haskell, but it still is a functor in mathematical sense.

So, to emphasize, I state it again: "Non-endo" functors ARE used in programming, and probably even more often than endofunctors.

The point here is that category theory is a highly abstract theory - It provides notions for abstracting concrete objects. We can define these notions to mean different things in different contexts.

And Hask (or Set, or subcategories of Set) is just one of these infinite definitions, which makes

  • arrows to be functions
  • objects to be types (or, sets)
  • composition to be function composition (.)
  • id arrow to be the id function.

Compare this "categorical universe" definition with the "categorical monoid" definition above - congrats, you've known two different takes on categories now!

To conclude, remember that category theory itself is just some abstractions. Abstractions themselves have no meaning and no use at all. We connect them to real things, and only in this way they can bring us convenience. Understand abstract concepts through concrete examples, but NEVER simplify these concepts themselves to anything concrete (Like, never simplify functors to merely the functors within a certain "categorical universe" (e.g. Hask, Set, etc)!).

P.S. If you ask "Is there a functor that sends Hask to another category in Haskell?" then the answer can be yes or no. For example, you can define a category Hask * Hask to contain any two types' cartesian product, and a functor data Diag a = Diag a a, fmap f x = Diag (f x) (f x) that sends each type A to its square A * A. However, Hask * Hask is still a subcategory of Hask, so we may say this is an endofunctor too.

daylily
  • 876
  • 5
  • 11
  • Good explanation, thank you. In fact I sort of understood what you answered here already, but I would like to hear and confirm from other guy who also knows very well. In fact, in other comments, more than one guy told me "that is not a functor" mentions. Also I feel endofunctor in Haskell leads lots of misconception because of the name of type class `functor`. Thanks again. – smooth_writing Aug 26 '20 at 21:55
  • @smooth_writing Maybe I worded that poorly. I did not mean to be insulting and I apologize if I came across that way. My point was that some people confuse functors with arrows. Those two concepts are not identical, though, which is what I was trying to say. – David Young Aug 27 '20 at 02:07
  • No, you worded properly, perhaps my "other guy who also knows very well" lead misunderstanding, but which meant you are the guy in this case. – smooth_writing Aug 27 '20 at 07:46
  • Having said that, I think arrows(morphisms) and functors are ultimately identical entities which can be exchangeable each other depending on perspective since ultimately any objects are some categories. – smooth_writing Aug 27 '20 at 07:49
4

The short answer: yes, there are 'smaller' categories in Haskell, and you can define functors (not just endofunctors) between them. Whether they are useful is another question.

This is something that I've been wondering about for years. The present question prompted me to take a stab at this. I'm currently making my way through Bartosz Milewski's Category Theory for Programmers for the third time. I'm not sure I got the following right, so I'd appreciate feedback.

Hask

If I understand it correctly, Hask is essentially the category of types (~ category of sets) with bottom (⊥) thrown in to represent non-terminating computation. Here's an attempt at illustrating it:

The category of Hask illustrated as a set of types, plus bottom

Each object in Hask is a type like Int, Bool, String, or your own custom types like Reservation, Order, etc. A type can be viewed as a set; e.g. Bool is the set containing True and False, String is the set of all strings, etc. Clearly, many of those sets (like String) are infinite.

In addition, there's also the special bottom object.

You can map types to other types, but you can't map to something outside of Hask because Hask encompasses all types and expressions:

Mapping from Hask to Hask

Here I've illustrated mappings from Hask to Hask by duplicating Hask, but really, the two categories are just two identical images.

A functor is a mapping that not only maps objects, but also morphisms between objects. Much has already been said about this, so the only point I'll make here is that since functors between Hask and Hask don't leave the category, they're functors within Hask, and thus endofunctors. That's the Functor type class in Haskell.

Unit category

The question, then, is: are there 'smaller' categories within Hask?

As far as I can tell: yes, infinitely many.

One of the simplest categories that exist is a category with a single object and no other morphisms than the identity morphism:

Unit category

In Haskell, this could be a picture of the unit (()) type. While () is part of Hask, you can also view it as a category in itself. Let's call it Unit.

Free categories

The above Unit category is just an example of a free category. A free category is a category constructed from a directed graph. Here's another graph:

Graph with two vertices and two edges

This one has two vertices and two edges. We can construct a category from this graph by interpreting the vertices as objects and the edges as morphisms. We also have to add identity morphisms for each object, as well as composition of morphisms.

In programming, a set with two objects is equivalent to a type with only two inhabitants. You can give these values various names, but such a type is always isomorphic to Bool.

Functor

Can we define a mapping between the above two categories?

Yes, we can do this by embedding Unit in the 'larger' category. We do that by just arbitrarily pick one of the objects:

Functor between Unit and the free category example

Another functor exists that picks the other object.

This is clearly a mapping between categories, so isn't an endofunctor. Is it a proper functor, though?

In order to be a functor, the mapping must not only map objects to objects, but also morphisms to morphisms. This is also the case here, because Unit only has the identity morphism. Thus, we also map the identity morphism to the identity morphism on the target object we've picked. The only compositions possible in Unit is id ∘ id, id ∘ id ∘ id, and so on. These all map to id ∘ id, id ∘ id ∘ id, etc. on the target object.

I've only been dabbling with category theory for a few years, but I think that this is a proper functor.

The Haskell Category type class

Haskell defines a type class called Category. It doesn't quite fit the above Unit category, or the above free category example, because it assumes that Category is a higher-kinded type (i.e. that it involves types) in Hask. Still, let's see if we can shoehorn Unit and the above free category into Category, as well as make a functor out of it.

Unit as Category

Instances of Category must be higher-kinded types (i.e. cat a b), so we can't just turn () into a Category instance. We can, however, define a higher-kinded type isomorphic to it:

data U a b = U deriving (Eq, Show)

Like the Const functor, this type defines type variables that it then ignores. Just like (), the U type has only one value, also called U. (Exercise: show that U and () are isomorphic.)

We can make U a Category instance:

instance Category U where
  id = U
  U . U = U

Is it a proper category, though? Does it obey the laws?

We can use equational reasoning to prove that it does:

Right identity

  U . id
= { definition of (.) }
  U

Left identity

  id . U
= { definition of (.) }
  U

Associativity

  U . (U . U)
= { definition of (.) }
  U . U
= { redundant brackets }
  (U . U)
= { definition of (.) }
  (U . U) . U

That looks good to me.

The free category example as Category

How about the above example of a free category? Like the above U type, this tiny category can't be parametrically polymorphic, but again we can define a phantom type:

data Bendo a b = Bendo { runB :: Bool -> Bool }

other :: Bendo a b
other = Bendo not

I've called the type Bendo for Boolean endomorphism, because that's what it turns out to be. The edges between the two objects (True and False) corresponds to picking the other object, which is equivalent to the the built-in not function.

To model the category in question, the only morphisms allowed are other and id, so other functions Bool -> Bool (like \_ -> True) should be disallowed. Thus, a module defining Bendo shouldn't export the data constructor.

Can we make Bendo a Category instance?

instance Category Bendo where
  id = Bendo id
  (Bendo f) . (Bendo g) = Bendo (f . g)

Indeed, this is possible. I'm not going to prove that this is a category, because it's really just the -> category instance specialised to (->) Bool Bool.

Functor

Let's now define a functor between U and Bendo. To do that, we can use the more general definition of Functor given in Control.Categorical.Functor. To make all this work, then, I've had to hide the usual definitions given in Prelude:

import Control.Category
import Control.Categorical.Functor
import Prelude hiding (id, (.), Functor(..))

We're also going to need to support MultiParamTypeClasses:

{-#LANGUAGE MultiParamTypeClasses #-}

In order to implement that more general Functor type class, we need a higher-kinded type. Again, let's produce another phantom type for the purpose:

data Embed a = Embed deriving (Eq, Show)

This is enough to define the instance:

instance Functor Embed U Bendo where
  fmap U = Bendo id

This maps U to the identity morhism in Bendo.

It's a bit awkward to use, but it's possible:

> (runB $ (fmap U :: Bendo (Embed a) (Embed b))) False
False
> (runB $ (fmap U :: Bendo (Embed a) (Embed b))) True
True

Haskell can't figure out what the type of fmap U is going to be, so you have to tell it. Once you tell it that the result should have the type Bendo (Embed a) (Embed b), fmap maps U to the identity morphism, which you can then verify by apply runB on either True or False.

Conclusion

Do functors (not just endofunctors) exist in programming? Yes, they do.

Are they useful? It seems to me that if you squint a little, those functors are just a subset of the 'normal' functions. A simplified version of the above functor is just:

uToBendo :: () -> Bool -> Bool
uToBendo () = id

This is just a normal function.

I have to think more about whether there's a more useful application when viewed like this.

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
  • Very informative and fun to read, thank you again! Here's my thought on the first part: **Hask**. – smooth_writing Aug 27 '20 at 08:28
  • Firstly, as I mentioned earlier in another comment thread, I think arrows(morphisms) and functors are ultimately identical entities which can be exchangeable each other depending on perspective since ultimately any objects are some categories. – smooth_writing Aug 27 '20 at 08:31
  • A morphism between identical categories: **Hask** to **Hask** is an endofunctor. This is the largest perspective. – smooth_writing Aug 27 '20 at 08:35
  • A morphism between different categories: `String` to `Bool` is a functor but not an endofunctor. Then, this functor is obviously useful in many ways. Just in case, we already agreed on `String` or `Bool` are categories. – smooth_writing Aug 27 '20 at 08:40
  • In fact, every function is a functor, since every object in the domain and range of function is a category with a single object. A functor is a morphism between categories, therefore, every function is a functor not limited to endofunctor. – smooth_writing Aug 27 '20 at 08:58
  • Not necessary to this answer, but I'll explain how **Hask** and **Set** are related. Haskell is in [System F](https://en.wikipedia.org/wiki/System_F) instead of [being dependently typed](https://en.wikipedia.org/wiki/Dependent_type), which means that one cannot mix types of different levels (the "types" are at level 1, "kinds" level 2, and so on); and **Hask** is merely the category of level-1 types (which can only contain primitive objects). – daylily Aug 27 '20 at 09:35
  • On the other hand, **Set** is said to contain all *small* sets as its objects; the "small" here is ambiguous, because it depends on the context, namely which *universe* we care about. A set being small means it belong to a certain universe. So, depending on which universe we're talking about, the **Set** will vary. In type theory, we have this hierachical universes system: `someValue : SomeType : Set0 : Set1 : ... : Setn : ...`, `:` meaning `be of type`. In Haskell, that is `someValue :: SomeType :: * :: * :: ...` where `*`s are actually on different levels. This often leads to confusion. – daylily Aug 27 '20 at 09:43
  • Finally, we can say that **Hask** is very similar to "a **Set** with respect to the universe `Set0` (denoted `*` in Haskell)"; This **Set**'s objects can only contain primitive objects, and we may also refer to these sets as *`Set0`-small* sets. – daylily Aug 27 '20 at 09:48
  • @smooth_writing I'm not sure that every function is a functor. A functor is not only a mapping of values, but also a mapping of morphisms. One of the things I learned by writing the above answer is that a type like `Bool`in itself isn't a category. You need morphisms as well. The one in the example is the monoidal category of (some) endomorphisms on `Bool`. AFAICT, there's at least four more monoidal categories involving `Bool`. So, you can't just talk about 'the category of `Bool`, or the category of `String`. – Mark Seemann Aug 27 '20 at 10:02
  • Here's a thing I discovered by myself. The dualism approach in many textbooks of category theory are very wrong and produce lots of confusion. Typically, in first place, we read there are fundamentally dual entities which are so-called "object" and "arrow". However, the fact is that only arrows exist. Category theory is actually monism. – smooth_writing Aug 27 '20 at 10:58
  • When a arrow forms a loop, something meaningful emerges and we call this "identity morphism". Or, we also call this "object". They are the same entity. – smooth_writing Aug 27 '20 at 11:16
  • In fact, the object 1 is also an unique morphism, or natural numbers are defined by F-algebra which is ultimately just morphisms. So, in category theory it's not like initially we have a pair of object&morphism, but only morphism. I think we simply pragmatically call the identity morphism as "object". – smooth_writing Aug 27 '20 at 11:30
  • Sorry, of course we need to double-check, but I think it's impossible that `Bool` itself without identity morphism and not as a category can exist in category theory from the first place. What want to emphasize is there is only arrows(morphisms) and this scales. It's like Fractal. – smooth_writing Aug 27 '20 at 11:36
  • In some scale, some perspective, `f` can be seen as a function or mophism, and in another scale, `f` becomes a functor, and another scale, a natural transformation. Well, there must be super natural formation, but I don't know the word. The reason is probably we don't actually need because if we just shift the scale, that thing becomes a mophism or functor etc. – smooth_writing Aug 27 '20 at 11:45
  • In functional programming, a functor is simply a higher order function. This strongly suggests the both are the same thing in different scale.Do you know pipleline-operaor? https://stackoverflow.com/questions/1457140/haskell-composition-vs-fs-pipe-forward-operator Pipeline-operator is basically function composition, but this really behaves like a functor, and actually it's an identity functor. – smooth_writing Aug 27 '20 at 11:52
  • I strongly believe Category theory has a perfect scale invariance, if not, such as a functor is always a higher order function, but a function is not always a functor, the theory would not work. – smooth_writing Aug 27 '20 at 12:09
  • I think you have to be more careful about `U`. One option is to talk about `U A B` for some specific `A` and `B`. Another option is to talk about the `U a b` for each `a` and `b`. This makes a lot more objects! Similarly, you can talk about the value `U @a @b` for any choices of `a` and `b`. So ... be specific. – dfeuer Aug 27 '20 at 22:11
  • A functor is a higher order function only if you treat morphisms as functions. – daylily Aug 28 '20 at 00:22
  • @H.Rhen I know, but I should have add the information in my comment. Thanks. – smooth_writing Aug 28 '20 at 07:04