packages feed

PenroseKiteDart-1.1.0: src/Try.hs

{-|
Module      : Tgraph.Try
Description : Result types for partial functions
Copyright   : (c) Chris Reade, 2021
License     : BSD-style
Maintainer  : chrisreade@mac.com
Stability   : experimental

Try is a synonym for Either String, which is used for results of partial operations
which return either Right something when defined or Left string when there is a problem
(where string is a failure report).
This is to allow computation to continue in failure cases without necessarily raising an error.
This module contains functions associated with Try results.
-}

module Try
  ( -- * Try - result types with failure reporting (for partial operations).
  Try
  , onFail
  , nothingFail
  , runTry
  , ifFail
  , isFail
  , concatFails
  , ignoreFails
  , atLeastOne
  , noFails
  ) where

import Data.Either(fromRight, lefts, rights, isLeft)


-- | Try is a synonym for Either String.  Used for results of partial functions
-- which return either Right something when defined or Left string when there is a problem
-- where string is a failure report.
-- Note: Either String (and hence Try) is a monad, and this is used frequently for combining  partial operations.
type Try a = Either String a

-- | onFail s exp - inserts s at the front of failure report if exp fails with Left report
onFail:: String -> Try a -> Try a
onFail s = either (Left . (s++)) Right

-- | Converts a Maybe Result into a Try result by treating Nothing as a failure
-- (the string s is the failure report on failure).
-- Usually used as infix (exp `nothingFail` s)
nothingFail :: Maybe b -> String -> Try b
nothingFail a s = maybe (Left s) Right a

-- |Extract the (Right) result from a Try, producing an error if the Try is Left s.
-- The failure report is passed to error for an error report.
runTry:: Try a -> a
runTry = either error id

-- |ifFail a tr - extracts the (Right) result from tr but returning a if tr is Left s.
ifFail :: a -> Try a -> a
ifFail = fromRight 

-- |a try result is a failure if it is a Left
isFail:: Try a -> Bool
isFail = isLeft
   
-- |Combines a list of Trys into a single Try with failure overriding success.
-- It concatenates all failure reports if there are any and returns a single Left r.
-- Otherwise it produces Right rs where rs is the list of all (successful) results.
-- In particular, concatFails [] = Right []
concatFails:: [Try a] -> Try [a]
concatFails ls = case lefts ls of
                 [] -> Right $ rights ls
                 other -> Left $ mconcat other -- concatenates strings for single report

-- |Combines a list of Trys into a list of the successes, ignoring any failures.
-- In particular, ignoreFails [] = []
ignoreFails:: [Try a] -> [a]
ignoreFails = rights

-- | atLeastOne rs - returns the list of successful results if there are any, but fails with an error otherwise.
-- The error report will include the concatenated reports from the failures. 
atLeastOne:: [Try a] -> [a]
atLeastOne [] = error "atLeastOne: applied to empty list.\n"
atLeastOne results = case ignoreFails results of
                 [] -> runTry $ onFail "atLeastOne: no successful results.\n" $ concatFails results
                 other -> other 

-- | noFails rs - returns the list of successes when all cases succeed, but fails with
-- an error and a concatenated failure report of all failures if there is at least one failure.
-- In particular, noFails [] = []
noFails:: [Try a] -> [a]
noFails = runTry . concatFails