ZipList
comes with a Functor
and an Applicative
instance (Control.Applicative) but why not Alternative
?
Bool
can be a monoid in two ways) and therefore neither should be the instance?I searched for "instance Alternative ZipList" (with the quotes to find code first) and only found the library, some tutorials, lecture notes yet no actual instance.
Matt Fenwick said ZipList A
will only be a monoid if A
is (see here). Lists are monoids though, regardless of the element type.
This other answer by AndrewC to the same question discusses how an Alternative
instance might look like. He says
There are two sensible choices for
Zip [1,3,4] <|> Zip [10,20,30,40]
:
Zip [1,3,4]
because it's first - consistent with MaybeZip [10,20,30,40]
because it's longest - consistent withZip []
being discarded
where Zip
is basically ZipList
.
I think the answer should be Zip [1,3,4,40]
. Let's see the instance:
instance Aternative Zip where
empty = Zip []
Zip xs <|> Zip ys = Zip (go xs ys) where
go [] ys = ys
go (x:xs) ys = x : go xs (drop 1 ys)
The only Zip a
we can produce without knowing the type argument a
is Zip [] :: Zip a
, so there is little choice for empty
. If the empty list is the neutral element of the monoid, we might be tempted to use list concatenation. However, go
is not (++)
because of the drop 1
. Every time we use one entry of the first argument list, we drop one off the second as well. Thus we have a kind of overlay: The left argument list hides the beginning of the right one (or all of it).
[ 1, 3, 4,40] [10,20,30,40] [ 1, 3, 4] [ 1, 3, 4]
^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
| | | | | | | | | | | | | |
[ 1, 3, 4] | [10,20,30,40] []| | | [ 1, 3, 4]
[10,20,30,40] [ 1, 3, 4] [ 1, 3, 4] []
One intuition behind ziplists is processes: A finite or infinite stream of results. When zipping, we combine streams, which is reflected by the Applicative
instance. When the end of the list is reached, the stream doesn't produce further elements. This is where the Alternative
instance comes in handy: we can name a concurrent replacement (alternative, really), taking over as soon as the default process terminates.
For example we could write fmap Just foo <|> pure Nothing
to wrap every element of the ziplist foo
into a Just
and continue with Nothing
afterwards. The resulting ziplist is infinite, reverting to a default value after all (real) values have been used up. This could of course be done by hand, by appending an infinite list inside the Zip
constructor. Yet the above is more elegant and does not assume knowledge of constructors, leading to higher code reusability.
We don't need any assumption on the element type (like being a monoid itself). At the same time the definition is not trivial (as (<|>) = const
would be). It makes use of the list structure by pattern matching on the first argument.
The definition of <|>
given above is associative and the empty list really is the empty element. We have
Zip [] <*> xs == fs <*> Zip [] == Zip [] -- 0*x = x*0 = 0
Zip [] <|> xs == xs <|> Zip [] == xs -- 0+x = x+0 = x
(fs <|> gs) <*> xs == fs <*> xs <|> gs <*> xs
fs <*> (xs <|> ys) == fs <*> xs <|> fs <*> ys
so all the laws you could ask for are satisfied (which is not true for list concatenation).
This instance is consistent with the one for Maybe
: choice is biased to the left, yet when the left argument is unable to produce a value, the right argument takes over. The functions
zipToMaybe :: Zip a -> Maybe a
zipToMaybe (Zip []) = Nothing
zipToMaybe (Zip (x:_)) = Just x
maybeToZip :: Maybe a -> Zip a
maybeToZip Nothing = Zip []
maybeToZip (Just x) = Zip (repeat x)
are morphisms of alternatives (meaning psi x <|> psi y = psi (x <|> y)
and psi x <*> psi y = psi (x <*> y)
).
Edit: For the some
/many
methods I'd guess
some (Zip z) = Zip (map repeat z)
many (Zip z) = Zip (map repeat z ++ repeat [])
There is in fact a sensible Alternative
instance for ZipList. It comes from a paper on free near-semirings (which MonadPlus
and Alternative
are examples of):
instance Alternative ZipList where
empty = ZipList []
ZipList xs <|> ZipList ys = ZipList $ go xs ys where
go [] bs = bs
go as [] = as
go (a:as) (_:bs) = a:go as bs
This is a more performant version of the original code for it, which was
ZipList xs <|> ZipList ys = ZipList $ xs ++ drop (length xs) ys
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