f
, but it was used to designate a functor / variable of type f
- there is no problem from the point of view of Haskell, but with inattentive reading this can be confusing, and I used the letter for morphism g
. A trifle, but still, I believe that it is useful to visually separate entities that have different nature. I will call ordinary types by their usual names, but I will call variable types of small Greek letters, simple ( ∗
) - letters from the beginning of the alphabet, and parametric ( ∗ → ∗
) - letters from the end of the alphabet ( θ
not from the end, but it looks better than χ
, which is too similar to X
). So, in the terminology category Hask :α, β, γ, δ ∷ ∗
θ, φ, ψ, ω ∷ ∗ → ∗
f, g, h ∷ α → β
∗ → ∗
class Functor
. Now you need to think about how a single morphism and composition are arranged in this category, so that they satisfy the axioms. type Id α = α
Id
cannot be entered completely like other functors, but if we could, we would write this: -- warning: - instance Functor Id where fmap ∷ (α → β) → (Id α → Id β) fmap f = f
That is, the behavior of this functor on morphisms is determined trivially: fmap ≡ id ∷ (α → β) → (α → β)
- there is no Id
constructor here, but this is the same signature. So, two mappings of the functor look like this: α ∷ ∗ ↦ Id α = α ∷ ∗ f ∷ α → β ↦ id f = f ∷ α → β
α ∷ ∗
as Id α ∷ ∗
, that is, under the action of an identical functor — this is also useful when we meet with monads.Maybe
and []
. Here is how they act on objects: α ∷ ∗ ↦ Maybe α ∷ ∗ β ∷ ∗ ↦ [β] ∷ ∗
α ↦ Maybe α ↦ [Maybe α]
Or in a different order: α ↦ [α] ↦ Maybe [α]
In order to apply the composition to type constructors, nothing special is needed - just write first one, then the other. For this operation, we introduce a notation similar to the usual composition (the type constructor in the form of an operator must begin with a colon, and I put the second just for symmetry): type (φ :.: ψ) α = φ (ψ α)
fmap
, we can say in general that we need to apply one fmap
to the morphism first, and then the second, that is, the display of the composition of functors will be: λf → fmap (fmap f)
or simply fmap . fmap
fmap . fmap
. It should be understood that these two fmap
are different manifestations of the same polymorphic function - each has its own type and corresponding definition. Let's demonstrate this clearly: instance Functor Maybe where -- fmap ∷ (α → β) → (Maybe α → Maybe β) fmap f = f' where f' Nothing = Nothing f' (Just x) = Just (fx) instance Functor [] where -- fmap ∷ (α → β) → ([α] → [β]) fmap g = g' where g' [] = [] g' (x:xs) = (gx) : (g' xs)
[] :.: Maybe
(fmap . fmap) ∷ (α → β) → ([Maybe α] → [Maybe β]) (fmap . fmap) even [Just 2, Nothing, Just 3] ≡ [Just True, Nothing, Just False]
Maybe :.: []
(fmap . fmap) ∷ (α → β) → (Maybe [α] → Maybe [β]) (fmap . fmap) even Just [1, 2, 3] ≡ Just [False, True, False] (fmap . fmap) even Nothing ≡ Nothing
Functor
, we would do it approximately as the authors of the TypeCompose package do . I, however, do not see much point in this, since I have to wrap the values in an additional data constructor. So, if we talk about functors as pairs of maps (on objects and morphisms), then for two functors (φ, fmap
φ
)
and (ψ, fmap
ψ
)
, their composition will be such a pair of maps: (φ :.: ψ, fmap
φ
. fmap
)
, i.e.α ∷ ∗ ↦ (φ :.: ψ) α ∷ ∗
f ∷ α → β ↦ (fmap
φ
. fmap
ψ
) f ∷ (φ :.: ψ) α → (φ :.: ψ) β
fmap
saves, then two successively applied fmap
will save: (fmap . fmap) id ≡ fmap (fmap id) ≡ fmap id ≡ id (fmap . fmap) (f . g) ≡ ≡ fmap (fmap (f . g)) ≡ ≡ fmap ((fmap f) . (fmap g)) ≡ ≡ (fmap (fmap f)) . (fmap (fmap g)) ≡ ≡ ((fmap . fmap) f) . ((fmap . fmap) g)
Q.E.D. The composition of functors, as well as the identical functor, is a natural and simple construction, but nevertheless useful, and we will need it again when we get to the monads. ((φ :.: ψ) :.: ω) α ≡ (φ :.: ψ) (ω α) ≡ ≡ φ (ψ (ω α)) ≡ ≡ φ ((ψ :.: ω) α) ≡ (φ :.: (ψ :.: ω)) α
The action on morphisms (fmap
φ
. fmap
ψ
)
is a common composition, we have already tested its associativity last time. (φ :.: Id) α ≡ φ (Id α) ≡ φ α ≡ Id (φ α) ≡ (Id :.: φ) α fmap . id ≡ id ≡ id . fmap
A monoid is a category with one object.
Int
. As morphisms, we take the following functions: λn → n + k
or shortly (+k)
for each k ∷ Int
, that is (+(-1))
, (+7)
, (+100500)
, etc. Together with the usual composition and (+0) ≡ id
, the category is obtained. One can imagine all Int
type values on the number line and the action of these morphisms on them as follows: Int: … -5 -4 -3 -2 -1 0 1 … (+(-2)) ∷ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ Int: … -3 -2 -1 0 1 2 3 … (+3) ∷ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ Int: … 0 1 2 3 4 5 6 …
That is, visually, these are shifts of the origin ( 0
) by a specified number of positions, although in general the numerical line remains the same. What the category structure gives us: firstly, there is a single morphism that leaves everything in place, secondly, there is a composition of morphisms, it is associative and id
neutral with respect to it. If m
and n
are two integers, then the composition is as follows: (+m) . (+n) ≡ (+(m+n))
That is, in fact, it acts as a sum. You can think of each morphism (+m)
as simply the number m
(where we moved the origin: (+m) 0 ≡ m
), then the “composition” m
and n
is really their sum: (m+n)
, and a single morphism is simply zero.A monoid is a triple consisting of a set, an associative binary operation on this set and an element neutral with respect to this operation.
(Int, (+), 0)
. (l + m) + n ≡ l + (m + n) m + 0 ≡ m ≡ 0 + m
Monoid
class in Haskell: class Monoid μ where mappend ∷ μ → μ → μ mempty ∷ μ
where μ
is the considered set, mappend
is an operation that must be associative, mempty
is an element that must be neutral with respect to mappend
. For a triple (Int, (+), 0)
embodiment of this class will be immediate: instance Monoid Int where mappend = (+) mempty = 0
(Float, (*), 1)
or ([α], (++), [])
or (Bool, (||), False)
, where ||
- logical “or”. But we can also implement a categorical definition, once again demonstrating their connection: type Mor α = α → α instance Monoid (Mor α) where mappend = (.) mempty = id
In this case, the type α ∷ ∗
is an object of category Hask , functions of type Mor α
are morphisms on this object. Together with the usual composition and single morphism id
they form a category with one object, that is, a monoid.Data.Monoid
module has a similar incarnation for the Endo
type. By the way, almost all incarnations in this module are made for types “in wrappers”. This is done because on the same set a monoidal structure can be introduced in different ways. For example, for the type Int
, besides the considered structure (Int, (+), 0)
(cf. Sum
), one can also consider (Int, (*), 1)
(cf. Product
), and for the type Bool
besides the one mentioned (Bool, (||), False)
(see Any
) is (Bool, (&&), True)
(see All
).The endofunctors of the Hask category , along with the composition of functors and the identical functor, have a monoid structure
Source: https://habr.com/ru/post/133579/
All Articles