Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What kind of morphism is `filter` in category theory?

In category theory, is the filter operation considered a morphism? If yes, what kind of morphism is it? Example (in Scala)

val myNums: Seq[Int] = Seq(-1, 3, -4, 2)

myNums.filter(_ > 0)
// Seq[Int] = List(3, 2) // result = subset, same type

myNums.filter(_ > -99)
// Seq[Int] = List(-1, 3, -4, 2) // result = identical than original

myNums.filter(_ > 99)
// Seq[Int] = List() // result = empty, same type
like image 566
Polymerase Avatar asked May 30 '18 01:05

Polymerase


People also ask

What is a morphism in category theory?

In mathematics, particularly in category theory, a morphism is a structure-preserving map from one mathematical structure to another one of the same type. The notion of morphism recurs in much of contemporary mathematics.

What is a limit in a category?

In category theory, a branch of mathematics, the abstract notion of a limit captures the essential properties of universal constructions such as products, pullbacks and inverse limits.

What do you mean by morphism?

The form –morphism means “the state of being a shape, form, or structure.” Polymorphism literally translates to “the state of being many shapes or forms.” What are some words that use the combining form –morphism? allomorphism.


3 Answers

To answer are question like this, I'd like to first understand what is the essence of filtering.

For instance, does it matter that the input is a list? Could you filter a tree? I don't see why not! You'd apply a predicate to each node of the tree and discard the ones that fail the test.

But what would be the shape of the result? Node deletion is not always defined or it's ambiguous. You could return a list. But why a list? Any data structure that supports appending would work. You also need an empty member of your data structure to start the appending process. So any unital magma would do. If you insist on associativity, you get a monoid. Looking back at the definition of filter, the result is a list, which is indeed a monoid. So we are on the right track.

So filter is just a special case of what's called Foldable: a data structure over which you can fold while accumulating the results in a monoid. In particular, you could use the predicate to either output a singleton list, if it's true; or an empty list (identity element), if it's false.

If you want a categorical answer, then a fold is an example of a catamorphism, an example of a morphism in the category of algebras. The (recursive) data structure you're folding over (a list, in the case of filter) is an initial algebra for some functor (the list functor, in this case), and your predicate is used to define an algebra for this functor.

like image 183
Bartosz Milewski Avatar answered Oct 03 '22 17:10

Bartosz Milewski


One interesting way of looking at this matter involves not picking filter as a primitive notion. There is a Haskell type class called Filterable which is aptly described as:

Like Functor, but it [includes] Maybe effects.

Formally, the class Filterable represents a functor from Kleisli Maybe to Hask.

The morphism mapping of the "functor from Kleisli Maybe to Hask" is captured by the mapMaybe method of the class, which is indeed a generalisation of the homonymous Data.Maybe function:

mapMaybe :: Filterable f => (a -> Maybe b) -> f a -> f b

The class laws are simply the appropriate functor laws (note that Just and (<=<) are, respectively, identity and composition in Kleisli Maybe):

mapMaybe Just = id
mapMaybe (g <=< f) = mapMaybe g . mapMaybe f

The class can also be expressed in terms of catMaybes...

catMaybes :: Filterable f => f (Maybe a) -> f a

... which is interdefinable with mapMaybe (cf. the analogous relationship between sequenceA and traverse)...

catMaybes = mapMaybe id
mapMaybe g = catMaybes . fmap g

... and amounts to a natural transformation between the Hask endofunctors Compose f Maybe and f.

What does all of that have to do with your question? Firstly, a functor is a morphism between categories, and a natural transformation is a morphism between functors. That being so, it is possible to talk of morphisms here in a sense that is less boring than the "morphisms in Hask" one. You won't necessarily want to do so, but in any case it is an existing vantage point.

Secondly, filter is, unsurprisingly, also a method of Filterable, its default definition being:

filter :: Filterable f => (a -> Bool) -> f a -> f a
filter p = mapMaybe $ \a -> if p a then Just a else Nothing

Or, to spell it using another cute combinator:

filter p = mapMaybe (ensure p)

That indirectly gives filter a place in this particular constellation of categorical notions.

like image 20
duplode Avatar answered Oct 03 '22 18:10

duplode


In this answer, I will assume that you are talking about filter on Set (the situation seems messier for other datatypes).

Let's first fix what we are talking about. I will talk specifically about the following function (in Scala):

def filter[A](p: A => Boolean): Set[A] => Set[A] = 
                                     s => s filter p

When we write it down this way, we see clearly that it's a polymorphic function with type parameter A that maps predicates A => Boolean to functions that map Set[A] to other Set[A]. To make it a "morphism", we would have to find some categories first, in which this thing could be a "morphism". One might hope that it's natural transformation, and therefore a morphism in the category of endofunctors on the "default ambient category-esque structure" usually referred to as "Hask" (or "Scal"? "Scala"?). To show that it's natural, we would have to check that the following diagram commutes for every f: B => A:

                       - o f
Hom[A, Boolean] ---------------------> Hom[B, Boolean]
     |                                       |
     |                                       |
     |                                       |
     | filter[A]                             | filter[B]
     |                                       |
     V                  ???                  V
Hom[Set[A], Set[A]] ---------------> Hom[Set[B], Set[B]]

however, here we fail immediately, because it's not clear what to even put on the horizontal arrow at the bottom, since the assignment A -> Hom[Set[A], Set[A]] doesn't even seem functorial (for the same reasons why A -> End[A] is not functorial, see here and also here).

The only "categorical" structure that I see here for a fixed type A is the following:

  • Predicates on A can be considered to be a partially ordered set with implication, that is p LEQ q if p implies q (i.e. either p(x) must be false, or q(x) must be true for all x: A).
  • Analogously, on functions Set[A] => Set[A], we can define a partial order with f LEQ g whenever for each set s: Set[A] it holds that f(s) is subset of g(s).

Then filter[A] would be monotonic, and therefore a functor between poset-categories. But that's somewhat boring.

Of course, for each fixed A, it (or rather its eta-expansion) is also just a function from A => Boolean to Set[A] => Set[A], so it's automatically a "morphism" in the "Hask-category". But that's even more boring.

like image 28
Andrey Tyukin Avatar answered Oct 03 '22 18:10

Andrey Tyukin