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

核能气质少年 提交于 2019-12-05 02:41:10

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.

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.

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.

filter can be written in terms of foldRight as:

filter p ys = foldRight(nil)( (x, xs) => if (p(x)) x::xs else xs ) ys

foldRight on lists is a map of T-algebras (where here T is the List datatype functor), so filter is a map of T-algebras.

The two algebras in question here are the initial list algebra

[nil, cons]: 1 + A x List(A) ----> List(A)

and, let's say the "filter" algebra,

[nil, f]: 1 + A x List(A) ----> List(A)

where f(x, xs) = if p(x) x::xs else xs.

Let's call filter(p, _) the unique map from the initial algebra to the filter algebra in this case (it is called fold in the general case). The fact that it is a map of algebras means that the following equations are satisfied:

filter(p, nil) = nil
filter(p, x::xs) = f(x, filter(p, xs))
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!