As the question says, why is toList (1, 2) == [2]
?
I remember something similar happening when fmapping on tuples, but I do not remember why or if it is
(1,2)
does not correspend to the list [1,2]
. That wouldn't make sense: what would then (True, 3.14)
correspend to? You can't have the list [True, 3.14]
, because a list can only contain elements of a single type. (Haskell is different from e.g. Python here.)
The only way to pick elements of guaranteed a single type from any tuple is, well, to take only a single element. Hence toList
, as generated from the Foldable (a,)
instance, takes tuples (a,b)
and yields lists [b]
. Obviously there's always exactly one b
element in such a tuple†.
You could in principle consider (Int, Int)
as a special case where the elements have the same type and hence you can pick two instead of one, but such a special handling would require some highly awkward type-equality checking. And generally, special-case handling is not a good idea.
Arguably, it would have been better not to define the Foldable (a,)
instance at all, to avoid this confusing behaviour. Then again, sometimes it's handy to use fold
to just get rid of the first tuple element (e.g. some index).
b
and not a
? Kind of arbitrary? Well, not completely. (a,b)
is actually syntactic sugar for (,) a b
, hence you can consider (,) a
as a functor (whose elements have type b
), but you can't have a functor (`(,)`b)
whose elements would have type a
.
If you are planning to use homogeneous pairs heavily, you may want to declare a new type which will precisely correspond to them. This way you'll be able to have access to the toList
you were expecting.
newtype Pair a = Pair { pair :: (a, a) }
instance Functor Pair where
fmap f (Pair (x, y)) = Pair (f x, f y)
instance Foldable Pair where
foldr f z (Pair (x, y)) = f x $ f y z
(a, b)
is fundamentally different from Pair a
or Constant (a, a) b
and it is important to clearly document which one you mean in your code if you want typeclass resolution to pick the right instance.
newtype Constant a b = Constant a
instance Functor (Constant a) where
fmap f (Constant a) = Constant a
instance Foldable (Constant a) where
foldr f z _ = z
Examples:
length (Constant (1, 2)) == 0
length (1, 2) == 1
length (Pair (1, 2)) == 2
The results do make more sense when you interpret the function names like this:
length
- how many values will be touched by fmap
, fold
etc.
toList
- what elements will be touched by fmap
, fold
etc.
As long as
length x == length (toList x)
the world is fine.