[Haskell-cafe] traversal transformations
Sjoerd Visscher
sjoerd at w3future.com
Sun Jun 14 19:29:04 EDT 2009
Hi,
While playing with Church Encodings of data structures, I realized
there are generalisations in the same way Data.Foldable and
Data.Traversable are generalisations of lists.
The normal Church Encoding of lists is like this:
> newtype List a = L { unL :: forall b. (a -> b -> b) -> b -> b }
It represents a list by a right fold:
> foldr f z l = unL l f z
List can be constructed with cons and nil:
> nil = L $ \f -> id
> cons a l = L $ \f -> f a . unL l f
Oleg has written about this: http://okmij.org/ftp/Haskell/zip-folds.lhs
Now function of type (b -> b) are endomorphisms which have a
Data.Monoid instance, so the type can be generalized:
> newtype FM a = FM { unFM :: forall b. Monoid b => (a -> b) -> b }
> fmnil = FM $ \f -> mempty
> fmcons a l = FM $ \f -> f a `mappend` unFM l f
Now lists are represented by (almost) their foldMap function:
> instance Foldable FM where
> foldMap = flip unFM
But notice that there is now nothing list specific in the FM type,
nothing prevents us to add other constructor functions.
> fmsnoc l a = FM $ \f -> unFM l f `mappend` f a
> fmlist = fmcons 2 $ fmcons 3 $ fmnil `fmsnoc` 4 `fmsnoc` 5
*Main> getProduct $ foldMap Product fmlist
120
Now that we have a container type represented by foldMap, there's
nothing stopping us to do a container type represented by traverse
from Data.Traversable:
{-# LANGUAGE RankNTypes #-}
import Data.Monoid
import Data.Foldable
import Data.Traversable
import Control.Monad
import Control.Applicative
newtype Container a = C { travC :: forall f b . Applicative f => (a ->
f b) -> f (Container b) }
czero :: Container a
cpure :: a -> Container a
ccons :: a -> Container a -> Container a
csnoc :: Container a -> a -> Container a
cpair :: Container a -> Container a -> Container a
cnode :: Container a -> a -> Container a -> Container a
ctree :: a -> Container (Container a) -> Container a
cflat :: Container (Container a) -> Container a
czero = C $ \f -> pure czero
cpure x = C $ \f -> cpure <$> f x
ccons x l = C $ \f -> ccons <$> f x <*> travC l f
csnoc l x = C $ \f -> csnoc <$> travC l f <*> f x
cpair l r = C $ \f -> cpair <$> travC l f <*> travC r f
cnode l x r = C $ \f -> cnode <$> travC l f <*> f x <*> travC r f
ctree x l = C $ \f -> ctree <$> f x <*> travC l (traverse f)
cflat l = C $ \f -> cflat <$> travC l (traverse f)
instance Functor Container where
fmap g c = C $ \f -> travC c (f . g)
instance Foldable Container where
foldMap = foldMapDefault
instance Traversable Container where
traverse = flip travC
instance Monad Container where
return = cpure
m >>= f = cflat $ fmap f m
instance Monoid (Container a) where
mempty = czero
mappend = cpair
Note that there are all kinds of "constructors", and they can all be
combined. Writing their definitions is similar to how you would write
Traversable instances.
So I'm not sure what we have here, as I just ran into it, I wasn't
looking for a solution to a problem. It is also all quite abstract,
and I'm not sure I understand what is going on everywhere. Is this
useful? Has this been done before? Are there better implementations of
foldMap and (>>=) for Container?
Finally, a little example. A Show instance (for debugging purposes)
which shows the nesting structure.
newtype ShowContainer a = ShowContainer { doShowContainer :: String }
instance Functor ShowContainer where
fmap _ (ShowContainer x) = ShowContainer $ "(" ++ x ++ ")"
instance Applicative ShowContainer where
pure _ = ShowContainer "()"
ShowContainer l <*> ShowContainer r = ShowContainer $ init l ++ ","
++ r ++ ")"
instance Show a => Show (Container a) where
show = doShowContainer . traverse (ShowContainer . show)
greetings,
--
Sjoerd Visscher
sjoerd at w3future.com
More information about the Haskell-Cafe
mailing list