A type which is a Bifunctor need not have the same number of a
values as b
values. Consider, for example,
data TwoLists a b = TwoLists [a] [b]
It is easy to implement bimap
, but othermap
is a real problem, especially if one of the lists is empty.
othermap f (TwoLists [] (b:bs)) = TwoLists [] _
What can you do here? You need to call f
to convert all the bs
to a list of type [d]
, but you can only call that function if you have an a
in hand.
Perhaps even worse is a type which doesn't really ever have b
values at all:
data TaggedFunction k a b = TaggedFunction a (k -> b)
instance Bifunctor (TaggedFunction k) where
bimap f g (TaggedFunction a b) = TaggedFunction (f a) (g . b)
How can you implement othermap
for this type? You could update the function, because you have an a
in hand and will have a b
by the time you need a d
. But there's no way for you to replace the a
with a c
, because you can't get hold of a b
to call othermap
's function with.
So you can't put this function in Bifunctor. Perhaps you're asking, why not put this in a new class? I think leftroundabout is right that the class is too constraining to be useful. othermap
can be defined only when you have the same number of a
s and b
s in your structure, i.e. when your structure is some functor f
wrapped around a tuple of type (a, b)
. For example, instead of TwoLists we could have defined
newtype PairList a b = PairList [(a, b)]
and that can have an othermap
definition. But it would just be
othermap f (PairList vs) = PairList (fmap f vs)
Likewise instead of TaggedFunction we could have defined
newtype MultiFunction k a b = MultiFunction (k -> (a, b))
but the othermap
definition is again just a wrapped call to fmap
:
othermap f (MultiFunction g) = MultiFunction (fmap f g)
So perhaps the best way to imagine defining this abstraction would be as, not a typeclass function, but an ordinary function that operates over a type that captures this composition:
newtype TupleFunctor f a b = TupleFunctor (f (a, b))
othermap :: Functor f => ((a, b) -> (c, d))
-> TupleFunctor f a b -> TupleFunctor f c d
othermap f (TupleFunctor x) = TupleFunctor (fmap f x)