I argue that the FunctorFilter type class is unnecessary:
First of all, all instances need to have a well-defined empty (even though this is not encoded in the type-class itself, but if I send all values to None, then the resulting 'thing' must not contain any Bs, because I could only get a B from the A => Option[B] function).
Then there is the other thing, that all the None results disappear from the resulting Functor.
Let's try to define it for a very simple example, Option.
I start with Some(5), and I filter it with (_ => None). The outcome is None. Notice how the Some constructor changed to the None constructor. This is something that an ordinary Functor can't do!
This means that whatever FunctorFilter actually is, it needs to know how to produce new values based on the content of an existing value.
This is also evidenced by the fact that it allows you to reduce the number of elements in the container. How can we do that? For example, you can use something like Foldable's foldMap, to map all elements to some Monoid, then combine those. Since here, the resulting structure is F[B], it means that if we go the Foldable route to achieve filtering, we need a monoid instance for F[B]. (note that we already needed the empty). Actually, I need a Monoid[F[B]] for all B.
Using Foldable this way really just means that we filter the structure by rebuilding it, leaving out the elements we don't need.
Furthermore, under the premise that we have a Foldable that can filter things, I can specialise the foldMap function from def foldMap[A, B](fa: F[A])(f: A => B)(implicit B: Monoid[B]): B to
flatMap[A, B](fa: F[A])(afb: A => F[B]) => F[B], since we required a Monoid[F[B]] for all B. This would mean that all FunctorFilters admit a FlatMap instance too. But given the above instances, FunctorFilter doesn't add any extra value.
This would mean that a Foldable, a Monoid, and a FlatMap instance are a necessary and sufficient condition. Also note, however, that we never required the existence of a pure function, so a Monad constraint is not needed.
I couldn't come up with any other approach that doesn't eventually reduce to a Foldable one way or another. If someone could provide examples where this isn't the case, I would be very interested.
I argue that the
FunctorFiltertype class is unnecessary:First of all, all instances need to have a well-defined
empty(even though this is not encoded in the type-class itself, but if I send all values toNone, then the resulting 'thing' must not contain anyBs, because I could only get aBfrom theA => Option[B]function).Then there is the other thing, that all the
Noneresults disappear from the resulting Functor.Let's try to define it for a very simple example,
Option.I start with
Some(5), and I filter it with(_ => None). The outcome isNone. Notice how theSomeconstructor changed to theNoneconstructor. This is something that an ordinaryFunctorcan't do!This means that whatever
FunctorFilteractually is, it needs to know how to produce new values based on the content of an existing value.This is also evidenced by the fact that it allows you to reduce the number of elements in the container. How can we do that? For example, you can use something like
Foldable'sfoldMap, to map all elements to someMonoid, then combine those. Since here, the resulting structure isF[B], it means that if we go theFoldableroute to achieve filtering, we need a monoid instance forF[B]. (note that we already needed theempty). Actually, I need aMonoid[F[B]]for allB.Using
Foldablethis way really just means that we filter the structure by rebuilding it, leaving out the elements we don't need.Furthermore, under the premise that we have a
Foldablethat canfilterthings, I can specialise thefoldMapfunction fromdef foldMap[A, B](fa: F[A])(f: A => B)(implicit B: Monoid[B]): BtoflatMap[A, B](fa: F[A])(afb: A => F[B]) => F[B], since we required aMonoid[F[B]]for allB. This would mean that allFunctorFilters admit aFlatMapinstance too. But given the above instances,FunctorFilterdoesn't add any extra value.This would mean that a
Foldable, aMonoid, and aFlatMapinstance are a necessary and sufficient condition. Also note, however, that we never required the existence of apurefunction, so aMonadconstraint is not needed.I couldn't come up with any other approach that doesn't eventually reduce to a
Foldableone way or another. If someone could provide examples where this isn't the case, I would be very interested.