{-# LANGUAGE CPP #-} -- | A free "monoid sans laws" type (i.e., a "free pointed magma") with an -- illegal 'Monoid' instance, intended for debugging. -- -- An example use: We can see that the 'Foldable' instance for Data.Map in -- @containers-0.5.0.0@ generates a lot of 'mempty's (one per leaf): -- -- @ -- > 'foldMap' 'N' (M.fromList [(x,x) | x <- [1..5]]) -- (((ε ◇ N 1) ◇ ε) ◇ N 2) ◇ ((((ε ◇ N 3) ◇ ε) ◇ N 4) ◇ ((ε ◇ N 5) ◇ ε)) -- @ -- -- After a discussion with the maintainer, this is improved in -- @containers-0.5.5.1@: -- -- @ -- > 'foldMap' 'N' (M.fromList [(x,x) | x <- [1..5]]) -- (N 1 ◇ (N 2 ◇ N 3)) ◇ (N 4 ◇ N 5) -- @ -- -- But now we can see a discrepancy between the 'Foldable' and 'Traversable' -- instances: -- -- @ -- > 'foldMapDefault' 'N' (M.fromList [(x,x) | x <- [1..5]]) -- (((N 1 ◇ N 2) ◇ N 3) ◇ N 4) ◇ N 5 -- @ -- -- This is because an expression like @f '<$>' x '<*>' y '<*>' z@ generates a -- left-biased tree -- @(x '<>' y) '<>' z@ -- whereas the 'Foldable' instance -- makes a right-biased tree -- @x '<>' (y '<>' z)@. -- -- Due to the monoid laws, these sorts of issues are typically invisible unless -- you look for them. But they can make a performance difference. module Data.Monoid.Nonfree (N(..), (◇), ε, toN, fromN) where import Control.Applicative import Data.Monoid import Data.Foldable (Foldable(foldMap)) import Data.Traversable (Traversable(traverse, sequenceA), foldMapDefault, fmapDefault) -- | Nonfree nonmonoid. data N a = N a | NEmpty | NAppend (N a) (N a) instance Monoid (N a) where mempty = NEmpty mappend = NAppend #if MIN_VERSION_base(4,9,0) instance Semigroup (N a) where (<>) = NAppend #endif instance Traversable N where traverse f (N x) = N <$> f x traverse f NEmpty = pure NEmpty traverse f (NAppend a b) = NAppend <$> traverse f a <*> traverse f b instance Functor N where fmap = fmapDefault instance Foldable N where foldMap = foldMapDefault -- | The 'Show' instance uses short names to make the append trees readable. instance Show a => Show (N a) where showsPrec n (N a) = showString "N " . showsPrec 11 a showsPrec n NEmpty = showString "ε" showsPrec n (NAppend x y) = showParen (n > 0) $ showsPrec 1 x . showString " ◇ " . showsPrec 1 y -- | A synonym for 'mappend' ('<>'). (◇) :: Monoid m => m -> m -> m (◇) = mappend -- | A synonym for 'mempty'. ε :: Monoid m => m ε = mempty -- | A version of 'Data.Foldable.toList' that extracts the full monoid append -- tree rather than flattening it to a list. toN :: Foldable t => t a -> N a toN = foldMap N newtype FromN e a = FromN { runFromN :: N e -> a } instance Functor (FromN e) where -- We can't use fmap = liftA because our Applicative instance is illegal. fmap f (FromN k) = FromN (fmap f k) instance Applicative (FromN e) where pure x = FromN $ \r -> case r of NEmpty -> x _ -> error "FromN pure: invalid structure (expected NEmpty)" FromN mf <*> FromN mx = FromN $ \r -> case r of NAppend a b -> mf a (mx b) _ -> error "FromN (<*>): invalid structure (expected NAppend)" -- | Given a monoid append tree and a 'Traversable' structure with exactly the -- same shape, put values from the former into the latter. This will fail with -- an error if the structure isn't identical. fromN :: Traversable t => N b -> t a -> t b fromN n = (`runFromN` n) . traverse (\_ -> FromN deleaf) where deleaf :: N e -> e deleaf (N x) = x deleaf _ = error "fromN traverse: invalid structure (expected N)"