The category of sets is both cartesian monoidal and cocartesian monoidal. The types of the canonical isomorphisms witnessing these two monoidal structures are listed below:
type x + y = Either x y
type x × y = (x, y)
data Iso a b = Iso { fwd :: a -> b, bwd :: b -> a }
eassoc :: Iso ((x + y) + z) (x + (y + z))
elunit :: Iso (Void + x) x
erunit :: Iso (x + Void) x
tassoc :: Iso ((x × y) × z) (x × (y × z))
tlunit :: Iso (() × x) x
trunit :: Iso (x × ()) x
For the purposes of this question I define Alternative to be a lax monoidal functor from Hask under the Either tensor to Hask under the (,) tensor (and no more):
class Functor f => Alt f
where
union :: f a × f b -> f (a + b)
class Alt f => Alternative f
where
nil :: () -> f Void
The laws are just those for a lax monoidal functor.
Associativity:
fwd tassoc >>> bimap id union >>> union
=
bimap union id >>> union >>> fmap (fwd eassoc)
Left unit:
fwd tlunit
=
bimap nil id >>> union >>> fmap (fwd elunit)
Right unit:
fwd trunit
=
bimap id nil >>> union >>> fmap (fwd erunit)
Here is how to recover the more familiar operations for the Alternative typeclass in terms of the coherence maps of the lax monoidal functor encoding:
(<|>) :: Alt f => f a -> f a -> f a
x <|> y = either id id <$> union (Left <$> x, Right <$> y)
empty :: Alternative f => f a
empty = absurd <$> nil ()
I define Filterable functors to be oplax monoidal functors from Hask under the Either tensor to Hask under the (,) tensor:
class Functor f => Filter f
where
partition :: f (a + b) -> f a × f b
class Filter f => Filterable f
where
trivial :: f Void -> ()
trivial = const ()
Having for its laws just backwards lax monoidal functor laws:
Associativity:
bwd tassoc <<< bimap id partition <<< partition
=
bimap partition id <<< partition <<< fmap (bwd eassoc)
Left unit:
bwd tlunit
=
bimap trivial id <<< partition <<< fmap (bwd elunit)
Right unit:
bwd trunit
=
bimap id trivial <<< partition <<< fmap (bwd erunit)
Defining standard filter-y functions like mapMaybe and filter in terms of the oplax monoidal functor encoding left as an exercise to the interested reader:
mapMaybe :: Filterable f => (a -> Maybe b) -> f a -> f b
mapMaybe = _
filter :: Filterable f => (a -> Bool) -> f a -> f a
filter = _
The question is this: is every Alternative Monad also Filterable?
We can type tetris our way to an implementation:
instance (Alternative f, Monad f) => Filter f
where
partition fab = (fab >>= either return (const empty), fab >>= either (const empty) return)
But is this implementation always lawful? Is it sometimes lawful (for some formal definition of "sometimes")? Proofs, counterexamples, and/or informal arguments would all be very useful. Thanks.
Here goes an argument which is broadly supportive of your beautiful idea.
Part one: mapMaybe
My plan here is restating the problem in terms of
mapMaybe, hoping that doing so will bring us to more familiar ground. To do so, I will use a fewEither-juggling utility functions:(I took the first three names from relude, and the fourth from errors. By the way, errors offers
maybeToRightandrightToMaybeasnoteandhushrespectively, inControl.Error.Util.)As you noted,
mapMaybecan be defined in terms ofpartition:Crucially, we can also go the other way around:
This suggests it makes sense to recast your laws in terms of
mapMaybe. With the identity laws, doing so gives us a great excuse to forget entirely abouttrivial:As for associativity, we can use
rightToMaybeandleftToMaybeto split the law in three equations, one for each component we get from the successive partitions:Parametricity means
mapMaybeis agnostic with respect to theEithervalues we are dealing with here. That being so, we can use our little arsenal ofEitherisomorphisms to shuffle things around and show that [I] is equivalent to [II], and [III] is equivalent to [V]. We are now down to three equations:Parametricity allows us to swallow the
fmapin [I]:That, however, is simply...
... which is equivalent to the conservation/identity law from witherable's
Filterable:That
Filterablealso has a composition law:Can we also derive this one from our laws? Let's start from [III] and, once more, have parametricity do its work. This one is trickier, so I'll write it down in full:
In the other direction:
(Note: While
maybeToRight () . rightToMaybe :: Either a b -> Either () bis notid, in the derivation above the left values will be discarded anyway, so it is fair to strike it out as if it wereid.)Thus [III] is equivalent to the composition law of witherable's
Filterable.At this point, we can use the composition law to deal with [IV]:
This suffices to show your class amounts to a well-established formulation of
Filterable, which is a very nice result. Here is a recap of the laws:As the witherable docs note, these are functor laws for a functor from Kleisli Maybe to Hask.
Part two: Alternative and Monad
Now we can tackle your actual question, which was about alternative monads. Your proposed implementation of
partitionwas:Following my broader plan, I will switch to the
mapMaybepresentation:And so we can define:
Or, in a pointfree spelling:
A few paragraphs above, I noted the
Filterablelaws say thatmapMaybeis the morphism mapping of a functor from Kleisli Maybe to Hask. Since the composition of functors is a functor, and(=<<)is the morphism mapping of a functor from Kleisli f to Hask,(maybe empty return .)being the morphism mapping of a functor from Kleisli Maybe to Kleisli f suffices formapMaybeAMto be lawful. The relevant functor laws are:This identity law holds, so let's focus on the composition one:
Therefore,
mapMaybeAMis lawful iffmaybe empty return . g =<< empty = emptyfor anyg. Now, ifemptyis defined asabsurd <$> nil (), as you have done here, we can prove thatf =<< empty = emptyfor anyf:Intuitively, if
emptyis really empty (as it must be, given the definition we are using here), there will be no values forfto be applied to, and sof =<< emptycan't result in anything butempty.A different approach here would be looking into the interaction of the
AlternativeandMonadclasses. As it happens, there is a class for alternative monads:MonadPlus. Accordingly, a restyledmapMaybemight look like this:While there are varying opinions on which set of laws is most appropriate for
MonadPlus, one of the laws no one seems to object to is...... which is precisely the property of
emptywe were discussing a few paragraphs above. The lawfulness ofmmapMaybefollows immediately from the left zero law.(Incidentally,
Control.Monadprovidesmfilter :: MonadPlus m => (a -> Bool) -> m a -> m a, which matches thefilterwe can define usingmmapMaybe.)In summary:
Yes, the implementation is lawful. This conclusion hinges on the
emptybeing indeed empty, as it should, or on the relevant alternative monad following the left zeroMonadPluslaw, which boils down to pretty much the same thing.It is worth emphasising that
Filterableisn't subsumed byMonadPlus, as we can illustrate with the following counterexamples:ZipList: filterable, but not a monad. TheFilterableinstance is the same as the one for lists, even though theAlternativeone is different.Map: filterable, but neither a monad nor applicative. In fact,Mapcan't even be applicative because there is no sensible implementation ofpure. It does, however, have its ownempty.MaybeT f: while itsMonadandAlternativeinstances requirefto be a monad, and an isolatedemptydefinition would need at leastApplicative, theFilterableinstance only requiresFunctor f(anything becomes filterable if you slip aMaybelayer into it).Part three: empty
At this point, one might still wonder how big of a role
empty, ornil, really plays inFilterable. It is not a class method, and yet most instances appear to have a sensible version of it lying around.The one thing we can be certain of is that, if the filterable type has any inhabitants at all, at least one of them will be an empty structure, because we can always take any inhabitant and filter everything out:
The existence of
chop, though doesn't mean there will be a singlenilempty value, or thatchopwill always give out the same result. Consider, for instance,MaybeT IO, whoseFilterableinstance might be thought of as a way to censor results ofIOcomputations. The instance is perfectly lawful, even thoughchopcan produce distinctMaybeT IO Voidvalues that carry arbitraryIOeffects.On a final note, you have alluded to the possibility of working with strong monoidal functors, so that
AlternativeandFilterableare linked by makingunion/partitionandnil/trivialisomorphisms. Havingunionandpartitionas mutual inverses is conceivable but fairly limiting, given thatunion . partitiondiscards some information about the arrangement of the elements for a large share of instances. As for the other isomorphism,trivial . nilis trivial, butnil . trivialis interesting in that it implies there is just a singlef Voidvalue, something that holds for a sizeable share ofFilterableinstances. It happens that there is aMonadPlusversion of this condition. If we demand that, for anyu...... and then substitute the
mmapMaybefrom part two, we get:This property is known as the right zero law of
MonadPlus, though there are good reasons to contest its status as a law of that particular class.