I am learning from the "Free Applicative Functors". Surely, the question I am going to ask is kind of aside with respect to main idea of the paper, but still...
...on the page 6 there is an attempt to generalize Functor to MultiFunctor:
class Functor f ⇒ MultiFunctor f where
fmap0 :: a → f a
fmap1 :: (a → b) → f a → f b
fmap1 = fmap
fmap2 :: (a → b → c) → f a → f b → f c
...
I can not see how this definition is justified from the category theory's viewpoint: fmap2 seems to be just a bifunctor, i.e. a functor defined on a product category. By definition, product category is given by all possible ordered pairs of objects and morphisms are pairs as well, hence: fmap2 :: (a -> a', b -> b') -> (f a, f b) -> (f a', f b') looks and feels like more appropriate signature.
I can understand the way of thinking standing behing the (a -> b -> c) -> f a -> f b -> f c choice: it is just the most obvious way to take known (a -> b) -> f a -> f b signature and force it to work with binary functions, rather then unary. But isMultiFunctor (given by the definition above) actually a bi-/multifunctor in the sense that category theory expects it to be?
P.S. The reason why I am curious is that it seems like one can't get to the Applicative by generalizing Functor, though paper states that one can.
I think the category theory angle you are taking is wrong. There is a Bifunctor class (with a map of type (a -> b) -> (c -> d) -> f a c -> f b d) but that is not what this generalisation is. If one uncurries some functions then the signature of fmap2 looks like:
fmap2 :: ((a,b) -> c) -> (f a, f b) -> f c
And by considering fmap2 id, we see that what we are implementing is not a bifunctor but a cartesian functor (i.e. a monoidal functor between cartesian categories), with fmap2 id :: (f a, f b) -> f (a,b) being the natural transformation:
One can then get an applicative from this Multifunctor generalisation. Just change pure for fmap0 and (<*>) for fmap2 ($).
Let's start with the obvious: fmap0 is pure.
Here's one you made a mistake on: fmap2 is liftA2.
(bimap is very different - (a -> b) -> (c -> d) -> f a b -> f c d)
And if you go back to the definition of Applicative, you see that it has a default implementation of (<*>), which is liftA2 id, which allows you to define it in terms of pure and either liftA2 or (<*>).
So yes, that class is equivalent to Applicative.
Related
This is yet another Haskell-through-category-theory question.
Let's take something simple and well-known as an example. fmap?
So fmap :: (a -> b) -> f a -> f b, omitting the fact that f is actually a Functor. As far as I understand, (a -> b) -> f a -> f b is nothing but a syntax sugar for the (a -> b) -> (f a -> f b); hence conclusion:
(1) fmap is a function producing a function.
Now, Hask contains functions as well, so (a -> b) and, in particular, (f a -> f b) is an object of the Hask (because objects of the Hask are well-defined Haskell types - a-ka mathematical sets - and there indeed exists set of type (a -> b) for each possible a, right?). So, once again:
(2) (a -> b) is an object of the Hask.
Now weird thing happens: fmap, obviously, is a morphism of the Hask, so it is a function, that takes another function and transform it to a yet another function; final function hasn't been applied yet.
Hence, one needs one more Hask's morphism to get from the (f a -> f b) to the f b. For each item i of type a there exists a morphism apply_i :: (f a -> f b) -> f b defined as \f -> f (lift i), where lift i is a way to build an f a with particular i inside.
The other way to see it is GHC-style: (a -> b) -> f a -> f b. On the contrast with what I've written above, (a -> b) -> f a is mapping to the regular object of the Hask. But such a view contradicts fundamental Haskell's axiom - no multivariate functions, but applied (curried) alternatives.
I'd like to ask at this point: is (a -> b) -> f a -> f b suppose to be an (a -> b) -> (f a -> f b) -> f b, sugared for simplicity, or am I missing something really, really important there?
is (a -> b) -> f a -> f b suppose to be an (a -> b) -> (f a -> f b) -> f b, sugared for simplicity
No. I think what you're missing, and it's not really your fault, is that it's only a very special case that the middle arrow in (a -> b) -> (f a -> f b) can be called morphism in the same way as the outer (a -> b) -> (f a -> f b) can. The general case of a Functor class would be (in pseudo-syntax)
class (Category (──>), Category (~>)) => Functor f (──>) (~>) where
fmap :: (a ──> b) -> f a ~> f b
So, it maps morphisms in the category whose arrows are denoted ──> to morphisms in the category ~>, but this morphism-mapping itself is just plainly a function. Your right, in Hask specifically function-arrows are the same sort of arrows as the morphism arrows, but this is mathematically speaking a rather degenerate scenario.
fmap is actually an entire family of morphisms. A morphism in Hask is always from a concrete type to another concrete type. You can think of a function as a morphism if the function has a concrete argument type and a concrete return type. A function of type Int -> Int represents a morphism (an endomorphism, really) from Int to Int in Hask. fmap, however has type Functor f => (a -> b) -> f a -> f b. Not a concrete type in sight! We just have type variables and a quasi-operator => to deal with.
Consider the following set of concrete function types.
Int -> Int
Char -> Int
Int -> Char
Char -> Char
Further, consider the following type constructors
[]
Maybe
[] applied to Int returns a type we could call List-of-Ints, but we usually just call [Int]. (One of the most confusing things about functors when I started out was that we just don't have separate names to refer to the types that various type constructors produce; the output is just named by the expression that evaluates to it.) Maybe Int returns the type we just call, well, Maybe Int.
Now, we can define a bunch of functions like the following
fmap_int_int_list :: (Int -> Int) -> [Int] -> [Int]
fmap_int_char_list :: (Int -> Char) -> [Int] -> [Char]
fmap_char_int_list :: (Char -> Int) -> [Char] -> [Int]
fmap_char_char_list :: (Char -> Char) -> [Char] -> [Char]
fmap_int_int_maybe :: (Int -> Int) -> Maybe Int -> Maybe Int
fmap_int_char_maybe :: (Int -> Char) -> Maybe Int -> Maybe Char
fmap_char_int_maybe:: (Char -> Int) -> Maybe Char -> Maybe Int
fmap_char_char_maybe :: (Char -> Char) -> Maybe Char -> Maybe Char
Each of these is a distinct morphism in Hask, but when we define them in Haskell, there's a lot of repetition.
fmap_int_int_list f xs = map f xs
fmap_int_char_list f xs = map f xs
fmap_char_int_list f xs = map f xs
fmap_char_char_list f xs = map f xs
fmap_int_int_maybe f x = case x of Nothing -> Nothing; Just y -> Just (f y)
fmap_int_char_maybe f x = case x of Nothing -> Nothing; Just y -> Just (f y)
fmap_char_int_maybe f x = case x of Nothing -> Nothing; Just y -> Just (f y)
fmap_char_char_maybe f x = case x of Nothing -> Nothing; Just y -> Just (f y)
The definitions don't differ when the type of f differs, only when the type of x/xs differs. That means we can define the following polymorphic functions
fmap_a_b_list f xs = map f xs
fmap_a_b_maybe f x = case x of Nothing -> Nothing; Just y -> Just (f y)
each of which represents a set of morphisms in Hask.
fmap itself is an umbrella term we use to refer to constructor-specific morphisms referred to by all the polymorphic functions.
With that out of the way, we can better understand fmap :: Functor f => (a -> b) -> f a -> f b.
Given fmap f, we first look at the type of f. We might find out, for example, that f :: Int -> Int, which means fmap f has to return one of fmap_int_int_list or fmap_int_int_maybe, but we're not sure which yet. So instead, it returns a constrained function of type Functor f => (Int -> Int) -> f Int -> f Int. Once that function is applied to a value of type [Int] or Maybe Int, we'll finally have enough information to know which morphism is actually meant.
Now weird thing happens: fmap, obviously, is a morphism of the Hask, so it is a function, that takes another function and transform it to a yet another function; final function hasn't been applied yet.
Hence, one needs one more Hask's morphism to get from the (f a -> f b) to the f b. For each item i of type a there exists a morphism apply_i :: (f a -> f b) -> f b defined as \f -> f (lift i), where lift i is a way to build an f a with particular i inside.
The notion of application in category theory is modelled in the form of CCC's - Cartesian Closed Categories. A category 𝓒 is a CCC if you have a natural bijection 𝓒(X×Y,Z) ≅ 𝓒(X,Y⇒Z).
In particular this implies that there exists a natural transformation 𝜺 (the evaluation), where 𝜺[Y,Z]:(Y⇒Z)×Y→Z, such that for every g:X×Y→Z there exists a 𝝀g:X→(Y⇒Z) such that, g = 𝝀g×id;𝜺[Y,Z]. So when you say,
Hence, one needs one more Hask's morphism to get from the (f a -> f b) to the f b.
The way you go from (f a -> f b) to the f b, or using the notation above, from (f a ⇒ f b) is via 𝜺[f a,f b]:(f a ⇒ f b) × f a → f b.
The other important point to keep in mind is that in Category Theory "elements" are not primitive concepts. Rather an element is an arrow of the form 𝟏→X,where 𝟏 is the terminal object. If you take X=𝟏 you have that 𝓒(Y,Z) ≅ 𝓒(𝟏×Y,Z) ≅ 𝓒(𝟏,Y⇒Z). That is, the morphisms g:Y→Z are in bijection to elements 𝝀g:𝟏→(Y⇒Z).
In Haskell this means functions are precisely the "elements" of arrow types. So in Haskell an application h y would be modelled via the evaluation of 𝝀h:𝟏→(Y⇒Z) on y:𝟏→Y. That is, the evaluation of (𝝀h)×y:𝟏→(Y⇒Z)×Y, which is given by the composition (𝝀h)×y;𝜺[Y,Z]:𝟏→Z.
For the sake of completeness, this answer focuses on a point that was addressed in various comments, but not by the the other answers.
The other way to see it is GHC-style: (a -> b) -> f a -> f b. On the contrast with what I've written above, (a -> b) -> f a is mapping to the regular object of the Hask.
-> in type signatures is right-associative. That being so, (a -> b) -> f a -> f b is really the same as (a -> b) -> (f a -> f b), and seeing (a -> b) -> f a in it would be a syntactic mix-up. It is no different from how...
(++) :: [a] -> [a] -> [a]
... doesn't mean that partially applying (++) will give us an [a] list (rather, it gives us a function that prepends some list).
From this point of view, the category theory questions you raise (for instance, on "need[ing] one more Hask's morphism to get from the (f a -> f b) to the f b") are a separate matter, addressed well by Jorge Adriano's answer.
I can't find bifunctor analog of fmap.
Explanation:
Functor for objects - datatype constructor. Type -
a -> f a
Functor for functions - fmap. Type - (a -> b) -> (fa -> fb)
Bifunctor for objects - result of bimap f g, where f :: (a -> a'), g :: (b -> b'). Type - p a b -> p a' b'
Bifunctor for functions - ?. Type - p (a -> b) (c -> d) -> p (a' -> b') (c' -> d')
Here is why I think bifunctor have such type (am I right?) with some example
UPDATE
UPDATE2
p (a -> b) (c -> d) -> p (a' -> b') (c' -> d') in the image above is morphism from bifunctor to bifunctor and also profunctor (because all functions are profunctors)
Summary:
I've thought p (a -> b) (c -> d) -> p (a' -> b') (c' -> d') is bifunctor for functions, but it's not. Bifunctor for morphisms is bimap. Type: (a -> b) -> (α -> β) -> p a α -> p b β.
I've thought p (a -> b) (c -> d) -> p (a' -> b') (c' -> d') is something unusual, but it's not, it's just function
Functor for objects - datatype constructor. Type - a -> f a
Functor for functions - fmap. Type - (a -> b) -> (fa -> fb)
Although this makes broadly sense, it is important to realise that the arrows above have three different meanings.
Functor for objects - datatype constructor. Type - a ⟼ f a
Functor for functions - fmap. Type - (a ⟿ b) ⟶ (f a ⟿ f b)
where
⟼ is a type-level “maps-to symbol” that associates the type a with a type f a. This does not have anything to do with value-level functions whose domain is a and codomain f a. (Those are found in applicatives/monads, but that's a different story.)
⟿ is a type constructor for some morphism. In the Hask category, those morphisms happen to be Haskell functions, but that's just a special case.
⟶ is an actual function-type constructor.
You may for now forget about the distinction between the latter two, but ⟼ and ⟶ are really quite different conceptually†. Basically, ⟼ is like the arrow you write in a lambda
Maybe :: Type -> Type
Maybe = \a ⟼ Maybe a
whereas ⟶ is just a way to express that you're abstracting over function-things.
Another related thing that might not be clear is that the objects you're talking about are Haskell types. Not values (as OO objects are).
So, I would phrase the listing you gave above thus:
Functor
for objects: datatype constructor. Kind Type -> Type, mapping-association a ⟼ f a.
for morphisms: fmap. Type: (a -> b) -> (f a -> f b).
Bifunctor
for objects: datatype constructor. Kind Type×Type -> Type, or curried Type -> Type -> Type, mapping-association a ⟼ b ⟼ p a b.
for morphisms: bimap. Type: (a -> b) -> (α -> β) -> p a α -> p b β.
†Actually, Haskell does not have ⟼ or what you wrote with a -> f a. This would be a type-level lambda, but type-level functions can actually only be expressed as type families, i.e. the closest you could get to expressing a ⟼ f a is type instance Functored a = f a.
You don't need a Bifunctor instance for (->), just (,):
b1 :: a -> Id a
b2 :: a -> Id2 a
-- instance Bifunctor (,) where
-- bimap f g (x, y) = (f x, g y)
f :: (Int, Float) -> (Id Int, Id2 Float)
f = bimap b1 b2
A well-known alternative formulation of Applicative (see, e.g., Typeclassopedia) is
class Functor f => Monoidal f where
unit :: f ()
pair :: f a -> f b -> f (a, b)
This leads to laws that look more like typical identity and associativity laws than what you get from Applicative, but only when you work through pair-reassociating isomorphisms. Thinking about this a few weeks ago, I came up with two other formulations that avoid this problem.
class Functor f => Fapplicative f where
funit :: f (a -> a)
fcomp :: f (b -> c) -> f (a -> b) -> f (a -> c)
class Functor f => Capplicative f where
cunit :: Category (~>) => f (a ~> a)
ccomp :: Category (~>) => f (b ~> c) -> f (a ~> b) -> f (a ~> c)
It's easy to implement Capplicative using Applicative, Fapplicative using Capplicative, and Applicative using Fapplicative, so these all have equivalent power.
The identity and associativity laws are entirely obvious. But Monoidal needs a naturality law, and these must as well. How might I formulate them? Also: Capplicative seems to suggest an immediate generalization:
class (Category (~>), Functor f) => Appish (~>) f where
unit1 :: f (a ~> a)
comp1 :: f (b ~> c) -> f (a ~> b) -> f (a ~> c)
I am a bit curious about whether this (or something similar) is good for something.
This is a really neat idea!
I think the free theorem for fcomp is
fcomp (fmap (post .) u) (fmap (. pre) v) = fmap (\f -> post . f . pre) (fcomp u v)
What is the general term for a functor with a structure resembling QuickCheck's promote function, i.e., a function of the form:
promote :: (a -> f b) -> f (a -> b)
(this is the inverse of flip $ fmap (flip ($)) :: f (a -> b) -> (a -> f b)). Are there even any functors with such an operation, other than (->) r and Id? (I'm sure there must be). Googling 'quickcheck promote' only turned up the QuickCheck documentation, which doesn't give promote in any more general context AFAICS; searching SO for 'quickcheck promote' produces no results.
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
(=<<) :: Monad m => (a -> m b) -> m a -> m b
Given that Monad is more powerful an interface than Applicative, this tell us that a -> f b can do more things than f (a -> b). This tells us that a function of type (a -> f b) -> f (a -> b) can't be injective. The domain is bigger than the codomain, in a handwavey manner. This means there's no way you can possibly preserve behavior of the function. It just doesn't work out across generic functors.
You can, of course, characterize functors in which that operation is injective. Identity and (->) a are certainly examples. I'm willing to bet there are more examples, but nothing jumps out at me immediately.
So far I found these ways of constructing an f with the promote morphism:
f = Identity
if f and g both have promote then the pair functor h t = (f t, g t) also does
if f and g both have promote then the composition h t = f (g t) also does
if f has the promote property and g is any contrafunctor then the functor h t = g t -> f t has the promote property
The last property can be generalized to profunctors g, but then f will be merely a profunctor, so it's probably not very useful, unless you only require profunctors.
Now, using these four constructions, we can find many examples of functors f for which promote exists:
f t = (t,t)
f t = (t, b -> t)
f t = (t -> a) -> t
f t = ((t,t) -> b) -> (t,t,t)
f t = ((t, t, c -> t, (t -> b) -> t) -> a) -> t
Also note that the promote property implies that f is pointed.
point :: t -> f t
point x = fmap (const x) (promote id)
Essentially the same question: Is this property of a functor stronger than a monad?
Data.Distributive has
class Functor g => Distributive g where
distribute :: Functor f => f (g a) -> g (f a)
-- other non-critical methods
Renaming your variables, you get
promote :: (c -> g a) -> g (c -> a)
Using slightly invalid syntax for clarity,
promote :: ((c ->) (g a)) -> g ((c ->) a)
(c ->) is a Functor, so the type of promote is a special case of the type of distribute. Thus every Distributive functor supports your promote. I don't know if any support promote but not Distributive.
Looking at the Haskell documentation, lifting seems to be basically a generalization of fmap, allowing for the mapping of functions with more than one argument.
The Wikipedia article on lifting gives a different view however, defining a "lift" in terms of a morphism in a category, and how it relates to the other objects and morphisms in the category (I won't give the details here). I suppose that that could be relevant to the Haskell situation if we are considering Cat (the category of categories, thus making our morphisms functors), but I can't see how this category-theoretic notion of a lift relates the the one in Haskell based on the linked article, if it does at all.
If the two concepts aren't really related, and just have a similar name, are the lifts (category theory) used in Haskell at all?
Lifts, and the dual notion of extensions, are absolutely used in Haskell, perhaps most prominently in the guise of comonadic extend and monadic bind. (Confusingly, extend is a lift, not an extension.) A comonad w's extend lets us take a function w a -> b and lift it along extract :: w b -> b to get a map w a -> w b. In ASCII art, given the diagram
w b
|
V
w a ---> b
where the vertical arrow is extract, extend gives us a diagonal arrow (making the diagram commute):
-> w b
/ |
/ V
w a ---> b
More familiar to most Haskellers is the dual notion of bind (>>=) for a monad m. Given a function a -> m b and return :: a -> m a, we can "extend" our function along return to get a function m a -> m b. In ASCII art:
a ---> m b
|
V
m a
gives us
a ---> m b
| __A
V /
m a
(That A is an arrowhead!)
So yes, extend could have been called lift, and bind could have been called extend. As for Haskell's lifts, I have no idea why they're called that!
EDIT: Actually, I think that again, Haskell's lifts are actually extensions. If f is applicative, and we have a function a -> b -> c, we can compose this function with pure :: c -> f c to get a function a -> b -> f c. Uncurrying, this is the same as a function (a, b) -> f c. Now we can also hit (a, b) with pure to get a function (a, b) -> f (a, b). Now, by fmaping fst and snd, we get a functions f (a, b) -> f a and f (a, b) -> f b, which we can combine to get a function f (a, b) -> (f a, f b). Composing with our pure from before gives (a, b) -> (f a, f b). Phew! So to recap, we have the ASCII art diagram
(a, b) ---> f c
|
V
(f a, f b)
Now liftA2 gives us a function (f a, f b) -> f c, which I won't draw because I'm sick of making terrible diagrams. But the point is, the diagram commutes, so liftA2 actually gives us an extension of the horizontal arrow along the vertical one.
"Lifting" comes up many times in functional programming, not only in fmap but in many other contexts. Examples of "liftings" include:
fmap :: (a -> b) -> F a -> F b where F is a functor
cmap :: (b -> a) -> F a -> F b where F is a contrafunctor
bind :: (a -> M b) -> M a -> M b where M is a monad
ap :: F (a -> b) -> F a -> F b where F is an applicative functor
point :: (_ -> a) -> _ -> F a where F is a pointed functor
filtMap :: (a -> Maybe b) -> F a -> F b where F is a filterable functor
extend :: (M a -> b) -> M a -> M b where M is a comonad
Other examples include applicative contrafunctor, filterable contrafunctor, and co-pointed functor.
All these type signatures are similar in one way: they map one kind of function between a and b into another kind of function between a and b.
In these different cases, the function types are not simply a -> b but have some kind of "twisted" types: e.g. a -> M b or F (a -> b) or M a -> b or F a -> F b and so on. However, each time the laws are very similar: twisted function types need to have identity and composition laws, and twisted composition needs to be associative.
For example, for applicative functors, we need to be able to compose functions of type F (a -> b). So we need to define a special "twisted" identity function (pure id :: F (a -> a) ) and a "twisted" composition operation, call it apcomp, with type signature F (a -> b) -> F (b -> c) -> F (a -> c). This operation needs to have identity and associativity laws. The ap operation needs to have identity and composition laws ("twisted identity maps to twisted identity" and "twisted composition maps to twisted composition").
Once we go through all these examples and derive the laws, we can prove that the laws turn out to be the same in all cases, if we formulate the laws via the "twisted" operations.
This is because we can formulate all these operations as functors in the sense of category theory. For example, for the applicative functor, we define two categories: the F-applicative category (objects a, b, ..., morphisms F(a -> b)) and the F-lifted category (objects F a, F b, ..., morphisms F a -> F b). A functor between these two categories requires us to have a lifting of morphisms, ap :: F(a -> b) -> F a -> F b. The laws of ap are completely equivalent to the standard laws of that functor.
Similar arguments hold for other typeclasses. We need to define categories, morphisms, composition operations, identity morphisms, and functors in each case. Once we verify that the laws hold, we will see that each of these typeclasses has an associated pair of categories and a functor between them, such that the laws of the typeclass are equivalent to the laws of these categories and the functor.
What have we gained? We have formulated the laws of many typeclasses in the same way (as the laws of categories and functors). This is a great economy of thought: we don't need to memorize all these laws each time; we can just memorize which categories and which functors need to be written down for each typeclass, as long as the methods of the typeclass can be reduced to some kind of "twisted lifting".
In this way, we can say that "liftings" are important and provide an application of category theory in functional programming.
I have made a presentation about this, https://www.youtube.com/watch?v=Zau8CxsfxOo and I'm writing a new free book where all derivations will be shown. https://github.com/winitzki/sofp