How is the Foldable instance of (,) useful?

174 Views Asked by At

I mistakenly applied length to a (pa,ir) and took a little while to find out, because the code would compile!

So I looked up :t length, which told me that its argument is only required to be a Foldable.

That's how I found that there's a Foldable instance for (,):

instance Foldable ((,) a) where
    foldMap f (_, y) = f y

    foldr f z (_, y) = f y z
    length _  = 1
    null _ = False

which incidentally overrides what would be the automatic definition of length.

What is a Foldable instance for (,) useful for? Especially considering that some of what derives from a minimal definition (null and length) of it has been overridden anyway.


Related question.

2

There are 2 best solutions below

7
duplode On BEST ANSWER

One way through which something nontrivial can be done with Foldable ((,) a) is bringing in the Foldable instance for Compose:

ghci> import Data.Functor.Compose
ghci> sum $ Compose [("apple", 1), ("banana", 2), ("orange", 3)]
6

Also, Foldable happens to be a superclass of Traversable, which in turn grants us sequenceA:

ghci> sequenceA ("strawberry", [4, 5, 6])
[("strawberry",4),("strawberry",5),("strawberry",6)]

The perspective these instances suggest is that of (a, b) values seen as b values annotated with an a tag. sequenceA, then, shifts the tag to the values in the other functorial structure (in the example above, a list).

2
chepner On

TL;DR Useful? Debatable. Consistent with other type constructors? Yes.

--

Consider the instance for Identity:

-- abbreviated to match the instance in your question
instance Foldable Identity where
    foldMap = coerce
    foldr f z (Identity x) = f x z
    length _ = 1
    null _ = False

This is consistent with treating Identity itself as isomorphic to (,) ():

i2t :: Identity a -> ((), a)
i2t (Identity x) = ((), x)

t2i :: ((), a) -> Identity a
t2i ((), x) = Identity x

The "usefulness" in each case is treating a tuple as a single value that "tagged" with a value of some other type. For (,) a, the tag has type a. For (,) () and Identity, the tag is either information-free or absent altogether. Folding such a type amounts to ignoring or stripping the tags and working directly with the wrapped value.

Similar instances could be defined for (,,) and so on, simply by adjusting the pattern that matches and ignores the tags. For example,

instance Foldable ((,,) a b) where
    foldMap f (_, _, y) = f y

    foldr f z (_, _, y) = f y z
    length _  = 1
    null _ = False

It seems you could similarly define an instance for the unit type (which is not included in Data.Foldable):

instance Foldable () where
    foldMap = coerce
    foldr f z () = f () z
    length () = 0
    null () = True

consistent with the interpretation of () as the empty tuple. Not only are there no tags, but there is no value to tag, either.

Either this is not a valid instance for reasons I'm overlooking, it was considered too useless even for the people that included Foldable ((,) a).

Of course, () is the actual empty product, not a *->*-kinded type constructor that can produce the unit type. As @duplode points out, there is a Foldable instance for such a constructor:

-- Adapted from the actual definitions in Data.Functor.Const
newtype Const a b = Const a 

instance Foldable (Const a) where
    foldr f z (Const _) = z

where Const () x is isomorphic to () for any type x.