Recently there was a question about the relation between DList <-> [] versus Codensity <-> Free.
This made me think whether there is such a thing for MonadPlus. The Codensity monad improves the asymptotic performance only for the monadic operations, not for mplus.
Moreover, while there used to be Control.MonadPlus.Free, it has been removed in favor of FreeT f []. And since there is no explicit free MonadPlus, I'm not sure how one would express a corresponding improve variant. Perhaps something like
improvePlus :: Functor f => (forall m. (MonadFree f m, MonadPlus m) => m a) -> FreeT f [] a
?
Update: I attempted to create such a monad using the backtracking LogicT monad, which seems to be defined in a way similar to Codensity:
newtype LogicT r m a = LogicT { unLogicT :: forall r. (a -> m r -> m r) -> m r -> m r }
and is suited for backtracking computations, that is, MonadPlus.
Then I defined lowerLogic, similar to lowerCodensity as followd:
{-# LANGUAGE RankNTypes, FlexibleInstances, FlexibleContexts, MultiParamTypeClasses,
UndecidableInstances, DeriveFunctor #-}
import Control.Monad
import Control.Monad.Trans.Free
import Control.Monad.Logic
lowerLogic :: (MonadPlus m) => LogicT m a -> m a
lowerLogic k = runLogicT k (\x k -> mplus (return x) k) mzero
Then, after supplementing the corresponding MonadFree instance
instance (Functor f, MonadFree f m) => MonadFree f (LogicT m) where
wrap t = LogicT (\h z -> wrap (fmap (\p -> runLogicT p h z) t))
one can define
improvePlus :: (Functor f, MonadPlus mr)
=> (forall m. (MonadFree f m, MonadPlus m) => m a)
-> FreeT f mr a
improvePlus k = lowerLogic k
However, something isn't right with it, as it seems from my initial experiments that for some examples k is distinct from improvePlus k. I'm not sure, if this is a fundamental limitation of LogicT and a different, more complex monad is needed, or just if I defined lowerLogic (or something else) wrongly.
The following is all based on my (mis)understanding of this very interesting paper posted by Matthew Pickering in his comment: From monoids to near-semirings: the essence of MonadPlus and Alternative (E. Rivas, M. Jaskelioff, T. Schrijvers). All results are theirs; all mistakes are mine.
From free monoids to
DListTo build up the intuition, first consider the free monoid
[]over the category of Haskell typesHask. One problem with[]is that if you havethen evaluating that requires traversing and re-traversing
xsfor each left-nested application ofmappend.The solution is to use CPS in the form of difference lists:
The paper considers the generic form of this (called the Cayley representation) where we're not tied to the free monoid:
with conversions
Two directions of generalization
We can generalize the above construction in two ways: first, by considering monoids not over
Hask, but over endofunctors ofHask; i.e. monads; and second, by enriching the algebraic structure into near-semirings.Freemonads toCodensityFor any Haskell (endo)functor
f, we can construct the free monadFree f, and it will have the analogous performance problem with left-nested binds, with the analogous solution of using the Cayley representationCodensity.Near-semirings instead of just monoids
This is where the paper stops reviewing concepts that are well-known by the working Haskell programmer, and starts homing in on its goal. A near-semiring is like a ring, except simpler, since both addition and multiplication are just required to be monoids. The connection between the two operations is what you expect:
where
(zero, |+|)and(one, |*|)are the two monoids over some shared base:The free near-semiring (over
Hask) turns out to be the followingForesttype:(good thing we don't have commutativity or inverses, those make free representations far from trivial...)
Then, the paper applies the Cayley representation twice, to the two monoidal structures.
(I've changed the implementation here slightly from the paper to emphasize that we are using the
Endostructure twice). When we'll generalize this, the two layers will not be the same. The paper then goes on to say:MonadPlusis almost a near-semiringThe paper then goes on to reformulate the
MonadPlustypeclass so that it corresponds to the near-semiring rules:(mzero, mplus)is monoidal:and it interacts with the monad-monoid as expected:
Or, using binds:
However, these are not the rules of the existing
MonadPlustypeclass frombase, which are listed as:The paper calls
MonadPlusinstances that satisfy the near-semiring-like laws "nondeterminism monads", and citesMaybeas an example that is aMonadPlusbut not a nondeterminism monad, since settingm1 = Just Nothingandm2 = Just (Just False)is a counter-example tojoin (m1 `mplus` m2) = join m1 `mplus` join m2.Free and Cayley representation of nondeterminism monads
Putting everything together, on one hand we have the
Forest-like free nondeterminism monad:and on the other, the double-Cayley representation of the two monoidal layers:
The paper gives the following example of a computation running in a nondeterminism monad that will behave poorly for
FreeP:Indeed, while
takes ages, the Cayley-transformed version
returns instantly.