packages feed

descript-lang-0.2.0.0: src/Descript/Misc/Error/Result/Base.hs

{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE DeriveFunctor #-}

-- | Datatypes which encode errors.
module Descript.Misc.Error.Result.Base
  ( ErrorMsg (..)
  , Result (..)
  , UResult
  , strToMsg
  , msgToStr
  , maybeToResult
  , maybeToUResult
  , eitherToResult
  , isFailure
  , isSuccess
  , mapSuccess
  , mapError
  ) where

import Descript.Misc.Summary
import Data.Monoid
import Data.Bifunctor
import Data.List
import Control.Applicative
import Control.Monad

-- | An error message. Like an enhanced string - when combined,
-- automatically adds "and".
newtype ErrorMsg = ErrorMsg [String] deriving (Eq, Ord, Read, Show, Monoid)

-- | Either produces a value or fails. This is isomorphic to 'Either',
-- but has an 'Alternative' and 'MonadPlus' implementation (for 'Monoid'
-- errors), and constructors are more descriptive ('Failure' and
-- 'Success' vs. 'Left' and 'Right').
data Result e a
  = Failure e
  | Success a
  deriving (Eq, Ord, Read, Show, Functor)

-- | Doesn't have any failure info.
type UResult a = Result () a

instance Bifunctor Result where
  bimap fe _ (Failure e) = Failure $ fe e
  bimap _ fx (Success x) = Success $ fx x

instance Applicative (Result e) where
  pure = Success
  Failure err <*> _ = Failure err
  Success _ <*> Failure err = Failure err
  Success f <*> Success x = Success $ f x

instance Monad (Result e) where
  return = pure
  Failure err >>= _ = Failure err
  Success x >>= f = f x

instance (Monoid e) => Alternative (Result e) where
  empty = Failure mempty
  Failure ex <|> Failure ey = Failure $ ex <> ey
  Failure _ <|> Success x = Success x
  Success x <|> _ = Success x

instance (Monoid e) => MonadPlus (Result e) where
  mzero = Failure mempty
  Failure ex `mplus` Failure ey = Failure $ ex <> ey
  Failure _ `mplus` Success x = Success x
  Success x `mplus` _ = Success x

instance Summary ErrorMsg where
  summary = msgToStr

instance (Summary e, Summary a) => Summary (Result e a) where
  summaryRec subSummary (Failure err) = "Failure: " ++ subSummary err
  summaryRec subSummary (Success x) = subSummary x

-- | Converts a user-freindly error message from 'String' to 'ErrorMsg'.
strToMsg :: String -> ErrorMsg
strToMsg x = ErrorMsg [x]

-- | Converts a user-freindly error message from 'ErrorMsg' to 'String'.
msgToStr :: ErrorMsg -> String
msgToStr (ErrorMsg xs) = intercalate ", and " xs

-- | If 'Just', a success. If 'Nothing', a failure with the given error.
maybeToResult :: e -> Maybe a -> Result e a
maybeToResult err Nothing = Failure err
maybeToResult _ (Just val) = Success val

-- | If 'Just', a success. If 'Nothing', a failure.
maybeToUResult :: Maybe a -> UResult a
maybeToUResult = maybeToResult ()

-- | Converts an 'Either', where 'Left' encodes a failure reason and
-- 'Right' encodes a result, into a 'Result'.
eitherToResult :: Either e a -> Result e a
eitherToResult (Left err) = Failure err
eitherToResult (Right val) = Success val

-- | Is the result a failure?
isFailure :: Result e a -> Bool
isFailure (Failure _) = True
isFailure (Success _) = False

-- | Is the result a success?
isSuccess :: Result e a -> Bool
isSuccess (Failure _) = False
isSuccess (Success _) = True

-- | If the result is a success, transforms it's value.
mapSuccess :: (a -> b) -> Result e a -> Result e b
mapSuccess = fmap

-- | If the result is a failure, transforms it's error.
mapError :: (e1 -> e2) -> Result e1 a -> Result e2 a
mapError f (Failure err) = Failure $ f err
mapError _ (Success x) = Success x