[Haskell-cafe] Foldable, Traversable and choosing an order

Juan Casanova juan.casanova at ed.ac.uk
Mon Sep 23 19:05:38 UTC 2019

Hello again, haskell-cafe.

I was trying to implement the Unifiable typeclass from  
Control.Unification on one of my types, and did so fine, but then  
realized I needed a Traversable instance.

So I thought about it, and concluded: Yes, my type definitely is  
Traversable, and I implemented Traversable. But then I realized I  
needed a Foldable instance for that.

And then I wasn't so sure anymore. I *can* implement a Foldable  
instance, but it requires a choice of the order in which my structure  
is traversed that I don't really want to make? A.k.a: I can think of  
at least two different ways of implementing Foldable that would give  
different results if the function passed to foldr was not associative.

So I looked online a bit, and it seems this is a topic people have  
talked about, but maybe not precisely in the context of the order.  
What I have gathered is that: yes, if a functor is Traversable, then  
there is at least one way to implement a Foldable in it. And I  
understand this, but then, I don't really grasp how that works in my  
specific case. Because my Traversable instance does not make a choice  
of this order (at least I think so), and I do need to make that choice  
to implement Foldable (at least I think so), and I don't see how the  
"default" implementation of foldMap in terms of traverse makes that  
choice. But the choice must be made somewhere if it is made in the  
Foldable. So what is going on?

Of course, here's the code. My types:

data SOTermPF fn p f = ConstF fn | Proj Int | CompF p [f] deriving Eq
newtype SOTermF fn f = SOF (SOTermPF fn f f) deriving Eq

My Traversable instance (with comments on intermediate types because  
otherwise it can get pretty obscure):

instance Traversable (SOTermF fn) where
	traverse f (SOF (ConstF c)) = pure (SOF (ConstF c))
	traverse f (SOF (Proj idx)) = pure (SOF (Proj idx))
	-- f g :: f b
	-- map f sargs = [f b]
	-- CompF :: a -> ([a] -> SOTermPF fn p a)
	-- (\h -> \ts -> SOF (CompF h ts)) :: ([a] -> SOTermF fn a)
	-- fmap (\h -> \ts -> SOF (CompF h ts)) (f g) :: f ([b] -> SOTermF fn b)
	-- traverse :: (aa -> ff bb) -> [aa] -> ff [bb]
	-- traverse :: (f b -> f b) -> [f b] -> f [b]
	-- ((fmap (\h -> \ts -> SOF (CompF h ts)) (f g)) <*>) :: f [b] -> f  
(SOTermF fn b)
	-- traverse id (map f sargs) :: f [b]
	-- (fmap (\h -> \ts -> SOF (CompF h ts)) (f g)) <*> (traverse id (map  
f sargs)) :: f (SOTermF fn b)
	traverse f (SOF (CompF g sargs)) = (fmap (\h -> \ts -> SOF (CompF h  
ts)) (f g)) <*> (traverse id (map f sargs))

But to implement Foldable I need to choose an order because in the  
case where I do have elements under the functor (CompF), I have them  
in two shapes: The "head" and the "arguments" (the p and the [f] in  
CompF p [f]). So, of course, the arguments are traversed in the order  
of the list, but the order that I need to choose is: Do I apply the  
head first and the arguments later or do I apply the arguments first  
and the head later? But my traverse seems so natural. Am I making that  
choice there? Maybe if I made the fmap over the arguments (while  
traversing the list) instead of over the head, and then did a flipped  
application of <*>? Does that mean my traverse instance has implicitly  
assumed that the head will be done first?

All of this is really out of curiosity and making sure I know what I'm  
doing: I will not use foldr on my structure and I'm pretty sure that  
the traversal that Control.Unification needs is associative. But I  
don't like seeing arbitrary choices appear out of nowhere in my code,  
I want to make sure of why I made them. :P

Thanks in advance,

The University of Edinburgh is a charitable body, registered in
Scotland, with registration number SC005336.

More information about the Haskell-Cafe mailing list