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
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.
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.
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.
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.
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.
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:
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
).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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With