bff-0.1: Data/Zippable/Definition.hs
{-# LANGUAGE ExistentialQuantification, PolymorphicComponents #-}
module Data.Zippable.Definition
( Zippable(..)
, CError
, throwCError
, cErrorToEither
) where
import Data.Traversable
-- | Efficient error reporting monad, as described in the paper \"Asymptotic
-- Improvement of Computations over Free Monads\" by Janis Voigtländer.
--
-- Can be used in places where one would use ('Either' String).
newtype CError a = CError (forall b. (a -> Either String b) -> Either String b)
instance Monad CError where
return a = CError (\h -> h a)
(CError p) >>= k = CError (\h -> p (\a -> case k a of CError q -> q h))
-- | Throw an error inside a 'CError' computation. Compare with 'Left' in the
-- ('Either' String) monad.
throwCError :: String -> CError a
throwCError err = CError (const (Left err))
-- | Run a 'CError' computation, and convert any error to 'Left' values.
cErrorToEither :: CError a -> Either String a
cErrorToEither (CError p) = p Right
-- | Data structures that can be folded.
--
-- Minimal complete definition: any of 'tryZipWith'' (preferred for efficiency), 'tryZipWith', or 'tryZip'
--
-- For example, given a data type:
--
-- > data Tree a = Leaf a | Node (Tree a) (Tree a)
--
-- a suitable instance would be
--
-- > instance Zippable Tree where
-- > tryZipWith' func (Leaf a) (Leaf b) = Right (Leaf (func a b))
-- > tryZipWith' func (Node a1 a2) (Node b1 b2) = do z1 <- tryZipWith' func a1 b1
-- > z2 <- tryZipWith' func a2 b2
-- > return (Node z1 z2)
-- > tryZipWith' _ _ _ = throwCError "Shape mismatch."
class Traversable k => Zippable k where
-- | Zip the elements of two structures with the given CError computation.
--
-- @'tryZipWith'' func x y = either 'throwCError' (Data.Traversable.mapM (uncurry func)) ('tryZip' x y)
tryZipWith' ::(a -> b -> CError c) -> k a -> k b -> CError (k c)
tryZipWith' func x y = either throwCError (Data.Traversable.mapM (uncurry func)) (tryZip x y)
-- | Zip the elements of two structures with the given function.
--
-- @'tryZipWith' func x y = 'cErrorToEither' ('tryZipWith'' (\a b -> return (func a b)) x y)@
tryZipWith :: (a -> b -> c) -> k a -> k b -> Either String (k c)
tryZipWith func x y = cErrorToEither (tryZipWith' (\a b -> return (func a b)) x y)
-- | Zip the elements of two structures as tuples.
--
-- @'tryZip' = 'tryZipWith' (,)@
tryZip :: k a -> k b -> Either String (k (a,b))
tryZip = tryZipWith (,)