Reputation: 4119
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?
Upvotes: 6
Views: 1698
Reputation: 34378
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.
Upvotes: 2
Reputation: 2300
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.
Upvotes: 3
Reputation: 531165
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.
Upvotes: 5
Reputation: 120711
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.
Upvotes: 6