Base provides ZipList, which is just a wrapper for []
where <*>
is based on zip
instead of cartesian-product. This isn't the default because it's not consistent with the Monad []
instance, but some people find it more intuitive, and both behaviors are useful in different contexts.
Edward Kmett provides Distributive, the dual of Traversable
. A Traversable can be mapped/pushed/distributed into any Applicative Functor; a Distributive can be pulled/factored out of any Functor. (For reasons I haven't unpacked, distribute
does not require the outer layer to be Applicative.)
Length-indexed lists are Distributive, and work how you'd expect. Specifically, their Applicative instance is based on zip
, just like ZipList
! This suggests ZipList
could also be Distributive
, which would be useful.
The docs for Distributive
note two things that must be true of any instance:
- "It [must be] isomorphic to
(->) x
for somex
."- A list is isomorphic to a partial function
Int ->
.
- A list is isomorphic to a partial function
- "[It] will need to have a way to consistently zip a potentially infinite number of copies of itself."
- This is more-or-less the raison d'être of
ZipList
.
- This is more-or-less the raison d'être of
Is that good enough? I spent a couple hours this afternoon trying to write instance Distributive ZipList where distributive = ...
and couldn't quite get it to work. For most functors f ZipList a
there's an obvious meaning to distribute f
, although I worry that might just be because I'm not thinking of enough non-Traversable functors.
Maybe
is tricky; shoulddistribute Nothing
be[]
orrepeat Nothing
? Butdistribute
is dual tosequenceA
, and theTraversable Maybe
instance says it should berepeat Nothing
.(->) e
might be the dealbreaker.- Intuitively
distribute $ const (repeat x) = repeat (const x)
. - We can extend that to any function that's guaranteed to return an infinite list; it looks kinda like
(\i -> (! i) <$> f) <$> [0..]
. - We can extend that to functions returning finite lists; we end up with an infinite list of partial functions. It's not obvious to me that that's unacceptable; partial functions show up all the time when working with lists.
- But that means
distribute $ const [] ≅ repeat undefined
, which is kinda silly. - The instance
Applicative ZipList
contains an important design decision:length (a <*> b) == min (length a) (length b)
(as opposed to an error or whatever). We're not leveraging that at all; the way I can see we might would bedistribute = const []
.
- Intuitively
Does anyone see a way forward?
If the partial-function interpretation were "acceptable", could we generalize that in any less-dumb way than distribute f = (\i -> (!! i) <$> f) <$> [0..]
?