
After writing some FunctorM instance for GHC-6.4, then converting them to Traversable instances for GHC-6.8, I found that most of the time, if not always, I actually needed a FunctorM instance (i.e. fmapM method), not a Traversable instance. Defining a Traversable instance requires me to also define a Foldable instance, i.e. a foldMap function. I had trees, lists and singletons extended by additional information, e.g. data PositionList a = PositionList [(Position, a)] data AsyncException e a = AsyncException (Maybe e) a However 'foldMap' cannot utilize the additional information, namely Position and (Maybe e), respectively. In the first case this is sad, in the second case I consider this bad, since it is too easy to throw away and thus ignore the exception (Maybe e). Some kind of fold is always useful on these structures, but 'foldMap' is too limited. For Traversable there is always both sequence and mapM (and their Applicative counterparts), although you need to define only one of them. However 'sequence' works on interim data structures that are not very natural. Imagine a data structure with a 'string' parameter that is intended for instantiation by String or ByteString. Now 'sequence' requires instantiations like (IO String) or (State s ByteString) which do not share common properties of String and ByteString, e.g. having a 'length' function. Although possible, those interim types look inappropriate to me. Even more, consider a data structure like StorableVector which requires certain properties of its elements. StorableVector is intended for Int, Double and other Storable types, but you cannot store (IO Double) values. This example is not practical, because the Storable restriction disallows to define both Traversable and FunctorM instances. However you see, that one cannot even imagine 'sequence' on StorableVector, whereas 'mapM' is possible, yet very inefficient (needs copying the entire vector for each added element). My conclusion is, that FunctorM was better than its reputation, and that Traversable as it is, is not what I need most of the time. What are the experiences of other Traversable users?

Henning Thielemann wrote:
? However 'foldMap' cannot utilize the additional information, namely Position and (Maybe e), respectively. In the first case this is sad, in the second case I consider this bad, since it is too easy to throw away and thus ignore the exception (Maybe e). Some kind of fold is always useful on these structures, but 'foldMap' is too limited.
I'm not sure what you mean, but if you define Traversable in a sensible way, then you can define Foldable with Data.Traversable.foldMapDefault and get a sensible definition.
Even more, consider a data structure like StorableVector which requires certain properties of its elements. StorableVector is intended for Int, Double and other Storable types, but you cannot store (IO Double) values. This example is not practical, because the Storable restriction disallows to define both Traversable and FunctorM instances. However you see, that one cannot even imagine 'sequence' on StorableVector, whereas 'mapM' is possible, yet very inefficient (needs copying the entire vector for each added element).
Sure you can define 'sequence'... because you cannot construct a StorableVector with elements that are not Storable. (You could save this information inside the StorableVector using existentials/GADTs.) Except...
My conclusion is, that FunctorM was better than its reputation, and that Traversable as it is, is not what I need most of the time. What are the experiences of other Traversable users?
that the fundamental complaint is that Traversables must be in Functor which requires to accomodate all types of kind *. On the other hand, as you note, either FunctorM or Traversable instance requires that property as well. (cf. Data.Traversable.fmapDefault, which uses "traverse" to define it) Basically, if you can make a type Traversable by defining (Traversable t) : traverse :: Applicative f => (a -> f b) -> t a -> f (t b) then this is almost always just as easy as defining (FunctorM f) : fmapM :: Monad m => (a -> m b) -> f a -> m (f b) for that type. (note that the type signatures are almost the same, and have almost equal power. Assuming you didn't want to do weird monadic more-complicated-than- Applicative things, that is.) In other words, my experience is that Traversable is more beautiful and no less usable. -Isaac

On Fri, 27 Feb 2009, Isaac Dupree wrote:
Henning Thielemann wrote:
? However 'foldMap' cannot utilize the additional information, namely Position and (Maybe e), respectively. In the first case this is sad, in the second case I consider this bad, since it is too easy to throw away and thus ignore the exception (Maybe e). Some kind of fold is always useful on these structures, but 'foldMap' is too limited.
I'm not sure what you mean, but if you define Traversable in a sensible way, then you can define Foldable with Data.Traversable.foldMapDefault and get a sensible definition.
This reminds me on using Data.Traversable.foldMapDefault for the definition of foldMap. So thanks for that hint! However, what I like to complain about is, that this foldMap is pretty useless, at least for me. Are there data structures beyond very simple ones (that is, list, tree, graphs of elements without additional information) where you can make any use of it?
participants (2)
-
Henning Thielemann
-
Isaac Dupree