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
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 isp LEQ q
ifp
impliesq
(i.e. eitherp(x)
must be false, orq(x)
must be true for allx: A
). - Analogously, on functions
Set[A] => Set[A]
, we can define a partial order withf LEQ g
whenever for each sets: Set[A]
it holds thatf(s)
is subset ofg(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))
来源:https://stackoverflow.com/questions/50595338/what-kind-of-morphism-is-filter-in-category-theory