There is a nice Free Alternative in the great free package, which lifts a Functor to a left-distributive Alternative.
That is, the claim is that:
runAlt :: Alternative g => (forall x. f x -> g x) -> Alt f a -> g a
is an Alternative homomorphism, with liftAlt. And, indeed, it is one, but only for left-distributive Alternative instances.
Of course, in reality, very few Alternative instances are actually left-distributive. Most of the alternative instances that actually matter (parsers, MaybeT f for most Monad f, etc.) are not left-distributive. This fact can be shown by an example where runAlt and liftAlt do not form an Alternative homomorphism:
(writeIORef x False <|> writeIORef True) *> (guard =<< readIORef x)
-- is an IO action that throws an exception
runAlt id $ (liftAlt (writeIORef x False) <|> liftAlt (writeIORef True))
*> liftAlt (guard =<< readIORef x)
-- is an IO action that throws no exception and returns successfully ()
So runAlt is only an Alternative homomorphism for some Alternatives, but not all. This is because the structure of Alt normalizes all actions to distribute over the left.
Alt is great because, structurally, Alt f is a lawful Applicative and Alternative. There isn't any possible way to construct a value of type Alt f a using the Applicative and Alternative functions that don't follow the laws...the structure of the type itself is what makes it a free Alternative.
Just like, for lists, you can't construct a list using <> and mempty that doesn't respect x <> mempty = x, mempty <> x = x, and associativity.
I've written a Free alternative that doesn't enforce the Applicative and Alternative laws, structurally, but does yield a valid Alternative and Applicative homomorphism with runAlt/liftAlt:
data Alt :: (* -> *) -> * -> * where
Pure :: a -> Alt f a
Lift :: f a -> Alt f a
Empty :: Alt f a
Ap :: Alt f (a -> b) -> Alt f a -> Alt f b
Plus :: Alt f as -> Alt f as -> Alt f as
instance Functor f => Functor (Alt f) where
fmap f = \case
Pure x -> Pure (f x)
Lift x -> Lift (f <$> x)
Empty -> Empty
Ap fs xs -> Ap ((f .) <$> fs) xs
Plus xs ys -> Plus (f <$> xs) (f <$> ys)
instance Functor f => Applicative (Alt f) where
pure = Pure
(<*>) = Ap
instance Functor f => Alternative (Alt f) where
empty = Empty
(<|>) = Plus
structurally, Alt f is not an actual Applicative, because:
pure f <*> pure x = Ap (Pure f) (Pure x)
pure (f x) = Pure (f x)
So pure f <*> pure x is not the same as pure (f x), structurally. Not a valid Applicative, right off the bat.
But, with the given runAlt and liftAlt:
liftAlt :: f a -> Alt f a
liftAlt = Lift
runAlt :: Alternative g => (forall x. f x -> g x) -> Alt f a -> g a
runAlt f = \case
Pure x -> pure x
Lift x -> f x
Empty -> empty
Ap fs xs -> runAlt f fs <*> runAlt f xs
Plus xs ys -> runAlt f xs <|> runAlt f ys
And runAlt here does indeed act as a valid Applicative homomorphism with a given natural transformation...
One could say that my new Alt f is a valid Alternative and Applicative when quotiented by the equivalence relationship defined by runAlt, i suppose.
Anyway, this is only slightly unsatisfying. Is there any way to write a free Alternative that is structurally a valid Alternative and Applicative, without enforcing left distributivity?
(In particular, I'm actually interested in one that follows the left catch law, and enforces it structurally. This would be a separate and also interesting thing, but not completely necessary.)
And, if there isn't any way, why not?
Control.Alternative.Free'sAlt fproduces a left-distributiveAlternativefor free, even iffisn'tAlternativeorfis a non-left-distributiveAlternative. We can say that, in addition to the well-agreed upon alternative lawsAlt falso gives left-distribution for free.Because
Alt fis always left distributive (andrunAlt . liftAlt = id)liftAltcan never be a homomorphism for non-left-distributiveAlternatives. If anAlternative fis not left-distributive, then there exista,b, andcsuch thatIf
liftAlt : f -> Alt fwere a homomorphism thenTo demonstrate this we need an
Alternativethat isn't left-distributive. Here's one,FlipAp [].Along with some laws for left distribution and right distribution, and some examples
We can demonstrate a few properties of lists,
FlipAplists, andrunAlt.Lists are left-distributive, but
FlipAplists aren'tLists aren't right-distributive, but
FlipAplists areAltis always left-distributiveAltis never right-distributiveWe can defile a right-distributive free alternative in terms of
FlipApandAlt.FlipApAltis never left-distributive.FlipApAltis always right-distributiveUp until now I haven't told you anything that you didn't already imply by saying that
liftAlt : f -> Alt fis anAlternativehomomorphism, but only for left-distributive Alternative instances. But I have shown you a free-alternative that isn't left-distributive (it's trivially right-distributive instead).A structurally valid free
AlternativeThis section answers the bulk of your question, is there a structurally valid free
Alternativethat isn't left-distributive? Yes.This isn't an efficient implementation; it's purpose is to demonstrate that it exists and that some version of it can be arrived at in a straight-forward manner.
To make a structurally valid free
AlternativeI am doing two things. The first is to create a data structure that can't represent any of theAlternativelaws; if it can't represent the law then a structure can't be constructed independently of the type class to violate it. This is the same trick used to make lists structurally obey theAlternativeassociativity law; there's no list that can represent the left-associated(x <|> y) <|> z. The second part is to make sure the operations obey the laws. A list can't represent the left association law, but an implementation of<|>could still violate it, likex <|> y = x ++ reverse y.The following structure can't be constructed to represent any of the
Alternativelaws.It's a
FunctorAnd it's
Applicative. Because the structure can't represent the laws, when we encounter a term containing one of the unpreventable expressions we're forced to convert it into something else. The laws tell us what to do.All of those
Aps could be covered by a pair of view patterns, but it doesn't make it any simpler.It's also an
Alternative. For this we'll use a view pattern to divide the cases into the empty and non-empty cases, and an extra type to store the proof that they're non-emptyliftAltandrunAltThis new
Alt fdoesn't provide either left-distribution or right-distribution for free, and thereforerunAlt id :: Alt f a -> g apreserves how distributivegis.Lists are still left-distributive, but
FlipAplists aren't.List's aren't right-distributive, but
FlipAplists still areSource code for this section
Structurally valid left-catch free
AlternativeTo control which laws we want we can add them to the structurally free alternative we made earlier.
To add left-catch we'll modify the structure so it can't represent it. Left catch is
(pure a) <|> x = pure a
To keep
Alt'from representing it we'll excludepurefrom what's allowed to the left ofPlus.This results in a compiler error in the implementation of
Alternative AltWhich we can fix by appealing to our new law,
(pure a) <|> x = pure a