1

In Category theory, it is very conspicuous that a definition of a functor should include two functions: on objects and on arrows. However, the usual Haskell Prelude.Functor does not make any mention of the former. Why is that? Would pure be that function on objects?

Specifically, for data F a to be an endofunctor in Hask, there should be given:

-- A function on objects:
omap ::     a     ->      F a

-- A function on arrows:
fmap :: (a -> a') -> (F a -> F a')

For a "small" type function, such as Maybe, it seems safe to name a suitable data constructor, such as Just, as the function on objects. For a type function with more variety — possibly infinite, like [⋅], — I am not so sure. Does it make difference whether I choose \x -> [ ] (a trivial functor), \x -> [x], \x -> [x, x], ..., or \x -> [x, x..]? Or would these be equally rightful, though distinct, instantiations of the "classical" categorial functor?

If so, would it be correct to propose that Prelude.Functor actually defines a whole family of functors at once?


The suggestion, offered in the answers to a question nearby, that the type constructor itself is the function on objects, I am not prepared to accept. We do require that a Functor instance gives lawful evidence to the existence of fmap. In the same way, we should be requiring that a term level function omap on objects is given, should we not? There must be some confusion about the type / term distinction in Hask here, either on my side alone, or in the common knowledge spread around the Haskell community.

To be specific, I may suggest that there are actually two distinct categories we may be talking about:

  • Hask — a category which objects are the sets of term level values of particular Haskell types, and arrows are the sets of term level functions.
  • Type — a category which objects are sets of types of a particular kind, and arrows are sets of type level functions, that is, types of a kind that has an arrow at the top level of its syntax tree.

Then, fmap is an arrow function for an endofunctor on Hask, while a type constructor is an arrow function for an endofunctor on Type, as it operates on sets of types, rather than values.

P.S. As @pigworker kindly explained in comments, my understanding of Hask is all wrong. However, I wonder if any of the above applies if a different category Term is considered, in which an object is a set of values (conceivably, all possible values of some chosen type), and an arrow is a (monomorphic) function between such sets. Since a hom set would also be an object, both fmap and omap (polymorphic functions — actually, bundles of arrows) would operate on terms. At the same time, my idea is that a subcategory of Term (in which only those sets of values that correspond to actual Haskell types are considered) may be made isomorphic to Hask with an appropriately chosen functor that takes class methods, newtypes and such into account. Are there some obvious problems with a construction along these lines?


Concerning the supposed duplicate question, the answers given there do not satisfy my curiosity, for the reasons outlined above. If Hask is not the appropriate category for my inquiry, than perhaps some other choice of category could be considered. So, I think an answer should necessarily include an argument as to why a certain category is chosen to work with, in the first place.

Ignat Insarov
  • 4,660
  • 18
  • 37
  • 10
    I'm sad to say that the thing that you say you are not prepared to accept is the truth. The objects of the "category" are *types*, not values in those types; the arrows are functions between those types. So the action on objects maps types to types, and the action on morphisms maps functions to functions. – pigworker Sep 07 '18 at 09:42
  • @pigworker Why then we require the definition of `fmap`, and not a _(hypothetical)_ type level function, such as `type F (a -> b) = F a -> F b`? – Ignat Insarov Sep 07 '18 at 09:46
  • The constraint `Functor f` indicates that `f` is an action on objects (a function from types to types) which can be extended to a functor by giving a suitable map on arrows (taking and function in some `(a -> b)` to a function in `(f a -> f b)`. There is no need for *another* function from types to types to complete the requirements of the definition. If `x` is a type, then so is `f x`, and it is because `f` is the action on objects that the type of `fmap` uses `f`. – pigworker Sep 07 '18 at 09:55
  • @pigworker So, what you are saying is that, in **Hask**, objects are _denotations for types_ _(say, `A :: Type` and `B :: Type`)_, while arrows are _terms_, particularly functions that map _terms_ of type `A` to _terms_ of type `B`. Right? Then, the object function for a functor would operate on type level, while the arrow function — on term level. – Ignat Insarov Sep 07 '18 at 10:01
  • Yes. And I wouldn't introduce the indirection of "denotations for". The objects are things that inhabit `Type`, and as `A -> B :: Type` when `A :: Type` and `B :: Type`, it is reasonable to take the values in `A -> B` as arrows (given that we have identity and composition for functions). It is also reasonable to play the same game one level up, taking the objects to be kinds and the arrows to be type-level functions. There are many categories in Haskell, not just types-and-functions-on-their-values. – pigworker Sep 07 '18 at 10:11
  • @pigworker Could there among these many categories be one that has sets of values as objects, and functions that connect such sets as arrows, where some polymorphic functions `omap :: a -> b` _(on a_ set of _sets of values)_ and `fmap :: (a -> a') -> (b -> b')` _(on a set of sets as well)_ could be defined and constitute an endofunctor? – Ignat Insarov Sep 07 '18 at 10:32
  • The type constructor *is* the "type-level function" you are looking for. – n. m. could be an AI Sep 07 '18 at 11:12
  • @n.m. I am unhappy with that answer. Are you certain it is the only answer possible? – Ignat Insarov Sep 07 '18 at 11:15
  • "or in the common knowledge spread around the Haskell community." Of the two possibilities you have entertained, this would be the rather less likely one. – n. m. could be an AI Sep 07 '18 at 11:17
  • @n.m. If that is your answer to my question, then it would appear to me as though you were being dogmatic. – Ignat Insarov Sep 07 '18 at 11:21
  • There are many possible answers, up to and including "Hask is [not a category](http://math.andrej.com/2016/08/06/hask-is-not-a-category/)". But in categories morally equivalent to Hask, the type-level function is absolutely definitely 100% sure the copy constructor, by definition. – n. m. could be an AI Sep 07 '18 at 11:23
  • "it would appear to me as though you were being dogmatic". Either that, or you are being hopelessly confused. Must be either one or the other, for sure. – n. m. could be an AI Sep 07 '18 at 11:27
  • @n.m. I think my confusions concerning objects of **Hask** were happily waved away by pigworker, even in the comments above, to which I made an edit to my question. And then I am still unhappy, both with the answers that are striking me as lacking, and with what seems to be an imposition of a dogma via appeals to authority and to my simple-mindedness. You are talking of me and of the Haskell community, with some irony as I would perceive, but you are not adding to my understanding as relates to the question; I do not see a logical argument in your comments. – Ignat Insarov Sep 07 '18 at 11:41
  • I'm not quite sure what exactly you are uncertain about, but it must be something pretty fundamental. I will post some sentences in the following comments that are absolute 100% self-evident truths. If you doubt any one them one tiny little bit, you should ask separate questions about them until either you or the rest of the world gain enlightenment. – n. m. could be an AI Sep 07 '18 at 12:14
  • 1
    (1) A category consists of *objects* and *arrows*, with some laws that govern their composition. (2) The category **Hask** has Haskell *types* as objects, and Haskell *functions* as arrows. (3) A type constructor of kind `*->*` is a function that sends Hask objects to Hask objects. (Not every function that sends Hask objects to Hask objects can be expressed as a Haskell type constructor). (4) A functor in Hask is a function that sends Hask objects to Hask objects, together with a function that sends Hask arrows to Hask arrows, with some laws that govern their interaction. – n. m. could be an AI Sep 07 '18 at 12:19
  • (5) Since Haskell type constructors are functions that send Haskell types to Haskell types, that is, Hask objects to Hask objects, they are good candidates to serve as the object half of Hask functors. (6) The arrow half can be realised as an ordinary Haskell higher-order polymorphic function. – n. m. could be an AI Sep 07 '18 at 12:23
  • @n.m. @n.m. Thank you, this explanation is quite vivid. My point is that, perhaps, some other choice of category may offer a way to think of another definition of functor, for which the definition of arrow function coincides with the usual `fmap`, but the definition of object function involves terms, rather than types. You are right in that another question should be asked about that, since this one went in a very unfortunate direction right from the start and, apparently, no edit could make the observer realize what I am actually trying to understand. – Ignat Insarov Sep 07 '18 at 12:35
  • There are many categories related to Haskell that we can talk about. Hask is one of them. The above sentences apply to Hask only. The categorical type-classes like Functor are thought to be working in Hask only. I'm not quite sure what you mean by "the definition of object function involves terms, rather than types". Where do you get this definition from? The object function, in any category, involves **objects**. Terms are not a basic notion of the category theory. – n. m. could be an AI Sep 07 '18 at 12:41
  • @n.m. Yes, to get such a definition, we need first to define a category such that objects in it are given via Haskell terms, rather than types. My confusion was that I thought in **Hask** sets of terms are objects, while it is actually _types_ as observed in type annotations. Happily, _pigworker_ explained the actual situation to me, and now my question becomes: why exactly **Hask** was chosen as _the_ de facto Haskell category and what happens if we consider functors in some other category that Haskell may represent. Perhaps `pure` would be an `omap` of such functors. – Ignat Insarov Sep 07 '18 at 12:53
  • 1
    We are not doing category theory in Haskell for the sake of doing category theory. We are doing it because *the category of types and functions is an extremely useful framework to reason about programming*. – n. m. could be an AI Sep 07 '18 at 13:00
  • *in Hask sets of terms are objects* This is not far from the truth, as types can be seen as sets of values that inhabit them (probably not terms though). That is. `Integer` is the set of all integers etc. – n. m. could be an AI Sep 07 '18 at 13:03
  • 2
    (1) As far as **Hask** is concerned, `pure` amounts to a natural transformation from the identity functor to some other **Hask** (endo)functor. (2) Values not being a primitive notion in this setting (cf. n.m.'s comments) is not really an obstacle. One way of handling them in terms of **Hask** objects and morphisms alone is working with functions from the terminal object, `()` (e.g. each `() -> Integer` function amounts to an `Integer` value). – duplode Sep 07 '18 at 13:41
  • @n.m. Yes, and now if there is a set of values `A` and a set of values `F A`, is it not natural to imagine there to be a function `omap :: A -> F A`? Clearly a type level function, such as `Maybe`, does not have anything to do with _sets of values_. Now I hope you can see exactly in what way I was confused. Concerning your promotion of **Hask** as an unquestionably distinguished category for dealing with Haskell, I would again say that I feel you are being dogmatic and needlessly restrictive. – Ignat Insarov Sep 07 '18 at 13:48
  • *is it not natural to imagine there to be a function omap :: A -> F A?* No, it is not. The definition of functor doesn't require that such a function exists. There are more advanced concepts, such as applicative functors, that do require such a function. It's called "pure" in this case. It has nothing to do with the object function of the functor, **which is `F`**. But a general functor doesn't have anything like `omap`. – n. m. could be an AI Sep 07 '18 at 14:04
  • To be a bit more specific, try to provide `omap` for the functor `(,) a`. You can find a definition [here](http://hackage.haskell.org/package/base-4.11.1.0/docs/src/GHC.Base.html#line-808). – n. m. could be an AI Sep 07 '18 at 14:10
  • @n.m. Let us go back a step. Suppose we regard sets of values as objects in a category **Hask'**, which is not far from the truth, as you noted above. Now, clearly a functor should offer an object function that has as its domain these sets of values. A type function does not have anything to do with sets of values, but a term level polymorphic function seems like it might. For your instance, `omap x = (mempty, x)` or `obimap x y = (x, y)` seem useful, but this is actually what my question is about; I do not know if such a functor is possible, but it is appealing to consider! – Ignat Insarov Sep 07 '18 at 14:16
  • A functor acts **on the entire category**. The domain of its object part is the set, or a class, or whatever collection it has of **objects** (types, in our case). A collection of **values** that belong to a single type has nothing to do with it. Your definition of `omap` doesn't work, try to compile it. You can't just guess what `mempty` means, it doesn't do what you think it does. And `obimap` is not `omap`. You seem mightily confused. Consider getting a Haskell book. – n. m. could be an AI Sep 07 '18 at 14:32
  • "Now, clearly a functor should offer an object function that has as its domain these sets of values". Clearly? Not by a long shot. This is a rather strange idea. Functors are not concerned with values at all. They deal with **objects** and **arrows**, period. A general category doesn't need to have any concept of a value, but functors are useful for these categories nevertheless; it follows that the notion of functor, if we want it to be generic, should not involve values. – n. m. could be an AI Sep 07 '18 at 14:39
  • @n.m. As I said: Let us go back a step. Suppose we regard sets of values as objects in a category **Hask'**. Now, clearly a functor _in this category_ should offer an object function that has as its domain these sets of values. Then again, there is no need to be condescending. There are some problems with my idea of a functor in **Hask'** — perhaps some usual functors could not be defined, perhaps there are even more serious flaws. Does not mean we should not try. Certainly does not mean your Haskell book contains the complete knowledge of what Haskell is or could ever be. – Ignat Insarov Sep 07 '18 at 14:54
  • 1
    "Clearly a type level function, such as `Maybe`, does not have anything to do with sets of values" -- Doesn't it? It takes us from a set of values (the inhabitants of, say, `Integer`) to a different set of values (the inhabitants of `Maybe Integer`). – duplode Sep 07 '18 at 14:58
  • @duplode I am not ready to agree with you about this. I can see how `f :: A -> B` takes us from a set of values of type `A` to a set of values of type `B`. For instance, `Just` tangibly takes us from one set of terms to another. `Maybe` merely takes us from a type tag to another type tag. Even though the two often coincide, I am not sure we should conflate one with the other right from the start. – Ignat Insarov Sep 07 '18 at 15:11
  • "clearly a functor in this category should offer an object function that has as its domain these sets of values.' A functor should do what a functor should do according to the definition of functor. Send objects to objects, and arrows to arrows, and nothing more. If you want more than that, you need something other than a functor, and you should be prepared to define what you want exactly. Anyway I don't think this discussion is productive. If you have a question, hit the question button. It looks like your question really is "why not all functors are applicatives", or something like that. – n. m. could be an AI Sep 07 '18 at 15:15
  • `Maybe` is an applicative functor, and `Just` is its `pure`. It looks like your question *really* is "why not all functors have `pure`" (which you seem to be calling `omap` for some reason), and this is a legitimate question. I tried to explain why not with the `(,)a` example but the space is limited. You may want to ask this question in an orderly manner. – n. m. could be an AI Sep 07 '18 at 15:22
  • @n.m. No, my question is not about that. I agree that we are not making progress here, and one reason for that, I think, is that you are trying to nudge me towards the thinking outlined in your _(metaphorically)_ Haskell book, while I am trying to look at things from a different _(and that may well be inconsistent, sure)_ perspective. If you start with the idea that something else than _types_ _(an abstract, intangible notion by itself)_ could be considered as objects for a category with the usual Haskell functions as arrows, I am sure you will understand the subject of my curiosity easily. – Ignat Insarov Sep 07 '18 at 15:32
  • 1
    "I am not ready to agree with you about this" -- Let's put it like this: `f :: A -> B` takes each value in the set `A` to a value in `B`. `Maybe :: Type -> Type` takes each *set* (`A`, `B`, `Integer`, etc.) to a different set (`Maybe A`, `Maybe B`, `Maybe Integer`, etc.). Looking at it like this, `Maybe` is a function taking sets to sets; the elements of those sets don't really come into play. – duplode Sep 07 '18 at 15:35
  • 1
    As for the other side of your question: if you want `pure` to be the object mapping of a functor, you have to (a) specify what the source category of the functor should be -- it's objects, morphisms, identities and composition, and ensure they follow the category laws; (b) do the same for the target category, if it isn't the same as the source one; and (c) specify the morphism mapping of the functor, while ensuring it follows the functor laws. – duplode Sep 07 '18 at 15:53
  • It seems to me that you are trying to look at things from a different perspective without fully understanding the standard perspective. I don't think the idea to look for categories with functions as arrows and something other than types as objects is a particularly productive one. I can't think of such category off the top of my head, and I have no idea what it could be useful for. You may give it a try if you want but I won't hold my breath. It is also not very useful to talh about Haskell type classes from such category perspective, because Haskell type classes are designed to work in Hask. – n. m. could be an AI Sep 07 '18 at 16:05
  • @duplode I understand that this abstraction can go some way. But surely there are sorts of sets it does not describe — say, even numbers. I can think of a total function `(/2) :: Even -> Integer`, for example, but it is outside of the Haskell type system. We do not have union, intersection, inclusion, negation. This is why it is hard for me to accept that the usual Haskell types are the only way to define objects in a category of Haskell functions, and hence that functors can only be defined via type level functions, though I agree that the usual functions are not very suitable for that too. – Ignat Insarov Sep 07 '18 at 16:59
  • 1
    I'd say that isn't really a problem. As far as the issue we are discussing here is concerned goes, things remain the same if we switch to a different type system with dependent types, or subtype polymorphism, etc. (And FWIW you can have an `Even` type in Haskell, even though the encoding won't be as neat aw what you might have with dependent types.) – duplode Sep 08 '18 at 03:51

0 Answers0