-- | -- A monoid that only admits a single value. module Web.Route.Invertible.Monoid.Exactly ( Exactly(..) , maybeToExactly , exactlyToMaybe , listToExactly , exactlyToList ) where import Control.Applicative (Alternative(..)) import Control.Monad (MonadPlus(..)) import Data.Semigroup (Semigroup((<>))) -- |A 'Maybe'-like monoid that only allows one value, overflowing into 'Conflict' when more than one 'Exactly' are combined (with '<|>' or 'Data.Monoid.<>', which thus function identically). data Exactly a = Blank -- ^ 'Nothing' | Exactly a -- ^ 'Just' | Conflict -- ^ 'fail': 'error' in most cases that attempt to access a value deriving (Eq, Ord, Show, Read) instance Functor Exactly where fmap _ Blank = Blank fmap f (Exactly a) = Exactly (f a) fmap _ Conflict = Conflict -- |Conflict always overrides other values. instance Applicative Exactly where pure = Exactly Exactly f <*> Exactly x = Exactly (f x) _ <*> Conflict = Conflict Conflict <*> _ = Conflict _ <*> _ = Blank -- |Same as for 'Maybe', except that @Exactly _ <|> Exactly _ = Conflict@. instance Alternative Exactly where empty = Blank Blank <|> e = e e <|> Blank = e _ <|> _ = Conflict instance Monad Exactly where Blank >>= _ = Blank Exactly x >>= f = f x Conflict >>= _ = Conflict Exactly _ >> e = e Conflict >> _ = Conflict _ >> Conflict = Conflict _ >> _ = Blank fail _ = Conflict instance MonadPlus Exactly instance Semigroup (Exactly a) where (<>) = (<|>) -- |Combines using the 'Alternative' instance, similar to an @'Data.Monoid.Alt' 'Maybe'@. instance Monoid (Exactly a) where mempty = Blank mappend = (<|>) instance Foldable Exactly where foldr _ z Blank = z foldr f z (Exactly x) = f x z foldr f z Conflict = f (error "foldr: Conflict") z foldl _ z Blank = z foldl f z (Exactly x) = f z x foldl f z Conflict = f z (error "foldl: Conflict") instance Traversable Exactly where traverse _ Blank = pure Blank traverse f (Exactly x) = Exactly <$> f x traverse _ Conflict = pure Conflict -- |@exactlyToMaybe . maybeToExactly == id@ maybeToExactly :: Maybe a -> Exactly a maybeToExactly Nothing = Blank maybeToExactly (Just x) = Exactly x -- |@exactlyToMaybe Conflict@ is an error. exactlyToMaybe :: Exactly a -> Maybe a exactlyToMaybe Blank = Nothing exactlyToMaybe (Exactly x) = Just x exactlyToMaybe Conflict = error "exactlyToMaybe: Conflict" -- |Conflict for any list with more than one element. listToExactly :: [a] -> Exactly a listToExactly [] = Blank listToExactly [x] = Exactly x listToExactly _ = Conflict -- |@exactlyToList Conflict@ is an error. exactlyToList :: Exactly a -> [a] exactlyToList Blank = [] exactlyToList (Exactly x) = [x] exactlyToList Conflict = error "exactlyToList: Conflict"