from-sum-0.2.3.0: Combinators for working with Maybe and Either

Copyright | (c) Dennis Gosnell 2016
License | BSD-style (see LICENSE file)
Maintainer | cdep.illabout@gmail.com
Stability | experimental
Portability | POSIX
Safe Haskell | Safe-Inferred
Language | Haskell2010

Control.FromSum

Contents

- Monadic in return value
- Monadic in both return and sum-type value
- Completely non-monadic functions
- Converting from `Maybe` to `Either`
- Collapsing funtions
- Converting to `ExceptT`
- Example converting to `ExceptT`

Description

This Haskell module exports various "from" functions for `Either` and `Maybe`.

Synopsis

- fromEitherM :: Applicative m => (e -> m a) -> Either e a -> m a
- fromEitherOrM :: Applicative m => Either e a -> (e -> m a) -> m a
- fromEitherM_ :: (Applicative m, Monoid b) => (e -> m b) -> Either e a -> m b
- fromEitherOrM_ :: (Applicative m, Monoid b) => Either e a -> (e -> m b) -> m b
- fromMaybeM :: Applicative m => m a -> Maybe a -> m a
- fromMaybeOrM :: Applicative m => Maybe a -> m a -> m a
- fromMaybeM_ :: (Applicative m, Monoid b) => m b -> Maybe a -> m b
- fromMaybeOrM_ :: (Applicative m, Monoid b) => Maybe a -> m b -> m b
- fromEitherMM :: Monad m => (e -> m a) -> m (Either e a) -> m a
- fromEitherOrMM :: Monad m => m (Either e a) -> (e -> m a) -> m a
- fromMaybeMM :: Monad m => m a -> m (Maybe a) -> m a
- fromMaybeOrMM :: Monad m => m (Maybe a) -> m a -> m a
- fromEither :: (e -> a) -> Either e a -> a
- fromEitherOr :: Either e a -> (e -> a) -> a
- fromMaybe :: a -> Maybe a -> a
- fromMaybeOr :: Maybe a -> a -> a
- maybeToEither :: e -> Maybe a -> Either e a
- maybeToEitherOr :: Maybe a -> e -> Either e a
- eitherToMaybe :: Either e a -> Maybe a
- collapseEither :: Either a a -> a
- collapseExceptT :: Monad m => ExceptT a m a -> m a
- collapseErrExceptT :: Monad m => ExceptT (m a) m a -> m a
- liftEitherExceptT :: forall (m :: Type -> Type) e a. Applicative m => Either e a -> ExceptT e m a
- fromEitherExceptT :: forall (m :: Type -> Type) e x a. Monad m => (e -> x) -> Either e a -> ExceptT x m a
- fromEitherOrExceptT :: forall (m :: Type -> Type) e a x. Monad m => Either e a -> (e -> x) -> ExceptT x m a
- fromEitherMExceptT :: Monad m => (e -> x) -> m (Either e a) -> ExceptT x m a
- fromEitherOrMExceptT :: Monad m => m (Either e a) -> (e -> x) -> ExceptT x m a
- fromMaybeExceptT :: forall (m :: Type -> Type) x a. Monad m => x -> Maybe a -> ExceptT x m a
- fromMaybeOrExceptT :: forall (m :: Type -> Type) a x. Monad m => Maybe a -> x -> ExceptT x m a
- fromMaybeMExceptT :: Monad m => x -> m (Maybe a) -> ExceptT x m a
- fromMaybeOrMExceptT :: Monad m => m (Maybe a) -> x -> ExceptT x m a
- guardExceptT :: forall (m :: Type -> Type) x. Monad m => Bool -> x -> ExceptT x m ()
- guardMExceptT :: Monad m => m Bool -> x -> ExceptT x m ()

# Monadic in return value

fromEitherM :: Applicative m => (e -> m a) -> Either e a -> m a Source #

A monadic version of `fromEither`.

     fromEitherM leftAction === either leftAction pure

    >>> fromEitherM (\s -> [length s]) $ Right 5
    [5]
    >>> fromEitherM (\s -> [length s]) $ Left ("foo" :: String)
    [3]

fromEitherOrM :: Applicative m => Either e a -> (e -> m a) -> m a Source #

A `flip`ed version of `fromEitherM`.

    >>> fromEitherOrM (Right 5) $ \s -> [length s]
    [5]

This can be nice to use as an error handler.

    >>> fromEitherOrM (Right 5) $ \s -> putStrLn ("error: " ++ s) >> undefined
    5
    >>> fromEitherOrM (Left "foo") $ \s -> putStrLn ("error: " ++ s) >> undefined
    error: foo
    ...

fromEitherM_ :: (Applicative m, Monoid b) => (e -> m b) -> Either e a -> m b Source #

Similar to `fromEitherM`, but only run the monadic `leftAction` if the `Either` argument is `Left`. Otherwise, return `pure` `mempty`.

     fromEitherM_ leftAction === either leftAction (const $ pure mempty)

    >>> fromEitherM_ (\err -> putStrLn err >> pure "bye") $ Right 5
    ""
    >>> fromEitherM_ (\err -> putStrLn err >> pure "bye") $ Left "there was an error"
    there was an error
    "bye"

This can be convenient when you want to run some sort of logging function whenever an `Either` is `Left`. If you imagine the logging function is `b -> `IO` `()``, then the effective type of `fromEitherM_` becomes ``fromEitherM_` :: (e -> `IO` `()`) -> `Either` e a -> `IO` `()``, because `()` has a `Monoid` instance, and `IO`, has an `Applicative` instance.

    >>> fromEitherM_ putStrLn $ Left "there was an error"
    there was an error

fromEitherOrM_ :: (Applicative m, Monoid b) => Either e a -> (e -> m b) -> m b Source #

A `flip`ed version of `fromEitherM_`.

fromMaybeM :: Applicative m => m a -> Maybe a -> m a Source #

A monadic version of `fromMaybe`.

     fromMaybeM nothingAction === maybe nothingAction pure

    >>> fromMaybeM [] $ Just 5
    [5]
    >>> fromMaybeM [] Nothing
    []

fromMaybeOrM :: Applicative m => Maybe a -> m a -> m a Source #

A `flip`ed version of `fromMaybeM`.

    >>> fromMaybeOrM (Just 5) []
    [5]

This can be nice to use as an error handler.

    >>> fromMaybeOrM (Just 5) $ putStrLn "some error occurred" >> undefined
    5
    >>> fromMaybeOrM (Nothing) $ putStrLn "some error occurred" >> undefined
    some error occurred
    ...

fromMaybeM_ :: (Applicative m, Monoid b) => m b -> Maybe a -> m b Source #

Similar to `fromMaybeM`, but only run the monadic `nothingAction` if the `Maybe` argument is `Nothing`. Otherwise, return `pure` `mempty`.

     fromMaybeM_ nothingAction === maybe nothingAction (const $ pure mempty)

    >>> fromMaybeM_ (putStrLn "hello" >> pure "bye") $ Just 5
    ""
    >>> fromMaybeM_ (putStrLn "hello" >> pure "bye") Nothing
    hello
    "bye"

This can be convenient when you want to run some sort of logging function whenever a `Maybe` is `Nothing`. If you imagine the logging function is ``IO` `()``, then the effective type of `fromMaybeM_` becomes ``fromMaybeM_` :: `IO` `()` -> `Maybe` a -> `IO` `()``, because `()` has a `Monoid` instance, and `IO`, has an `Applicative` instance.

    >>> fromMaybeM_ (putStrLn "hello") Nothing
    hello

fromMaybeOrM_ :: (Applicative m, Monoid b) => Maybe a -> m b -> m b Source #

A `flip`ed version of `fromMaybeM`.

# Monadic in both return and sum-type value

fromEitherMM :: Monad m => (e -> m a) -> m (Either e a) -> m a Source #

Similar to `fromEitherM` but the `Either` argument is also a monadic value.

    >>> fromEitherMM (\s -> [length s]) [Right 5, Right 10]
    [5,10]
    >>> fromEitherMM (\s -> [length s]) [Left ("foo" :: String), Right 100]
    [3,100]

NOTE: I don't particularly like the name of this function. If you have a suggestion for a better name, please submit a PR or issue.

fromEitherOrMM :: Monad m => m (Either e a) -> (e -> m a) -> m a Source #

A `flip`ed version of `fromEitherMM`.

fromMaybeMM :: Monad m => m a -> m (Maybe a) -> m a Source #

Similar to `fromMaybeM` but the `Maybe` argument is also a monadic value.

    >>> fromMaybeMM [] [Just 6, Just 5]
    [6,5]
    >>> fromMaybeMM [] [Just 6, Nothing, Just 7]
    [6,7]

NOTE: I don't particularly like the name of this function. If you have a suggestion for a better name, please submit a PR or issue.

fromMaybeOrMM :: Monad m => m (Maybe a) -> m a -> m a Source #

A `flip`ed version of `fromMaybeMM`.

# Completely non-monadic functions

fromEither :: (e -> a) -> Either e a -> a Source #

Similar to `fromMaybe`.

    >>> fromEither show $ Left 5
    "5"
    >>> fromEither show $ Right "hello"
    "hello"

fromEitherOr :: Either e a -> (e -> a) -> a Source #

A `flip`ed version of `fromEither`.

fromMaybe :: a -> Maybe a -> a Source #

The `fromMaybe` function takes a default value and a `Maybe` value. If the `Maybe` is `Nothing`, it returns the default value; otherwise, it returns the value contained in the `Maybe`.

#### Examples

Expand

Basic usage:

    >>> fromMaybe "" (Just "Hello, World!")
    "Hello, World!"

    >>> fromMaybe "" Nothing
    ""

Read an integer from a string using `readMaybe`. If we fail to parse an integer, we want to return `0` by default:

    >>> import GHC.Internal.Text.Read ( readMaybe )
    >>> fromMaybe 0 (readMaybe "5")
    5
    >>> fromMaybe 0 (readMaybe "")
    0

fromMaybeOr :: Maybe a -> a -> a Source #

A `flip`ed version of `fromMaybe`.

# Converting from `Maybe` to `Either`

maybeToEither :: e -> Maybe a -> Either e a Source #

Convert a `Maybe` to an `Either`.

If the `Maybe` is `Just`, then return the value in `Right`.

    >>> maybeToEither 3 $ Just "hello"
    Right "hello"

If the `Maybe` is `Nothing`, then use the given `e` as `Left`.

    >>> maybeToEither 3 Nothing
    Left 3

maybeToEitherOr :: Maybe a -> e -> Either e a Source #

A `flip`ed version of `maybeToEither`.

    >>> maybeToEitherOr (Just "hello") 3
    Right "hello"

    >>> maybeToEitherOr Nothing 3
    Left 3

eitherToMaybe :: Either e a -> Maybe a Source #

Convert an `Either` to a `Maybe`.

A `Right` value becomes `Just`.

    >>> eitherToMaybe $ Right 3
    Just 3

A `Left` value becomes `Nothing`.

    >>> eitherToMaybe $ Left "bye"
    Nothing

# Collapsing funtions

collapseEither :: Either a a -> a Source #

Collapse an ``Either` a a` to an `a`. Defined as ``fromEither` `id``.

Note: Other libraries export this function as `fromEither`, but our `fromEither` function is slightly more general.

    >>> collapseEither (Right 3)
    3
    >>> collapseEither (Left "hello")
    "hello"

collapseExceptT :: Monad m => ExceptT a m a -> m a Source #

Similar to `collapseEither`, but for `ExceptT`.

    >>> collapseExceptT (ExceptT $ pure (Right 3))
    3
    >>> collapseExceptT (ExceptT $ pure (Left "hello"))
    "hello"

collapseErrExceptT :: Monad m => ExceptT (m a) m a -> m a Source #

Collapse an `ExceptT` where the error returns the same type as the whole computation.

    >>> let exceptTOne = pure 3 :: ExceptT (IO Int) IO Int
    >>> collapseErrExceptT exceptTOne :: IO Int
    3

This is helpful when writing short-circuiting computations where you throw errors that match the type of the underlying computation.

    >>> :{
    let go :: Int -> ExceptT (IO ()) IO ()
        go x = do
          bar <-
            if x < 10
            then
              pure "hello"
            else
              throwE (putStrLn "Error occurred, x too big!")
          lift $ putStrLn $ bar ++ " world"
    :}

    >>> collapseErrExceptT (go 100) :: IO ()
    Error occurred, x too big!
    >>> collapseErrExceptT (go 3) :: IO ()
    hello world

In this example, the error type in the `ExceptT` is ``IO` ()`. This allows us to easily short-circuit the remaining computations. In this example, the remaining computation is just printing `bar `++` " world"`.

# Converting to `ExceptT`

liftEitherExceptT :: forall (m :: Type -> Type) e a. Applicative m => Either e a -> ExceptT e m a Source #

Lift an `Either` into an `ExceptT`.

This is the same as `liftEither`, but the return type is specialized for `ExceptT`.

    >>> liftEitherExceptT (Right 3) :: ExceptT String Identity Int
    ExceptT (Identity (Right 3))

Note that if you want to lift `m (`Either` e a)` to ``ExceptT` e m a`, just use `ExceptT`:

    >>> action = Identity (Left "error") :: Identity (Either String Int)
    >>> ExceptT action :: ExceptT String Identity Int
    ExceptT (Identity (Left "error"))

fromEitherExceptT :: forall (m :: Type -> Type) e x a. Monad m => (e -> x) -> Either e a -> ExceptT x m a Source #

Lift an `Either` to an `ExceptT` with a handler for transforming the error value.

If the input `Either` is `Right`, then just return it like normal:

    >>> let rightEither = Right () :: Either String ()
    >>> fromEitherExceptT (\str -> length str) rightEither :: ExceptT Int Identity ()
    ExceptT (Identity (Right ()))

If the input `Either` is `Left`, then pass the value to the handler:

    >>> let leftEither = Left "hello" :: Either String ()
    >>> fromEitherExceptT (\str -> length str) leftEither :: ExceptT Int Identity ()
    ExceptT (Identity (Left 5))

fromEitherOrExceptT :: forall (m :: Type -> Type) e a x. Monad m => Either e a -> (e -> x) -> ExceptT x m a Source #

Just like `fromEitherExceptT`, but the arguments are flipped.

fromEitherMExceptT :: Monad m => (e -> x) -> m (Either e a) -> ExceptT x m a Source #

Similar to `fromEitherExceptT` but the `Either` value is lifted in a `Monad`.

    >>> let identityLeft = Identity (Left "hello") :: Identity (Either String ())
    >>> fromEitherMExceptT (\str -> length str) identityLeft :: ExceptT Int Identity ()
    ExceptT (Identity (Left 5))

This is similar to `withExceptT`, but the second argument is the unwrapped `ExceptT` computation.

fromEitherOrMExceptT :: Monad m => m (Either e a) -> (e -> x) -> ExceptT x m a Source #

Just like `fromEitherOrMExceptT`, but the arguments are flipped.

fromMaybeExceptT :: forall (m :: Type -> Type) x a. Monad m => x -> Maybe a -> ExceptT x m a Source #

Lift a `Maybe` to an `ExceptT` with a default value for the case when the `Maybe` is `Nothing`.

If the `Maybe` is `Just`, then just return the value like normal:

    >>> let justVal = Just True :: Maybe Bool
    >>> fromMaybeExceptT 5 justVal :: ExceptT Int Identity Bool
    ExceptT (Identity (Right True))

If the `Maybe` is `Nothing`, then use the default value as the error value:

    >>> let nothingVal = Nothing :: Maybe Bool
    >>> fromMaybeExceptT 5 nothingVal :: ExceptT Int Identity Bool
    ExceptT (Identity (Left 5))

fromMaybeOrExceptT :: forall (m :: Type -> Type) a x. Monad m => Maybe a -> x -> ExceptT x m a Source #

Just like `fromMaybeExceptT` but with the arguments flipped.

fromMaybeMExceptT :: Monad m => x -> m (Maybe a) -> ExceptT x m a Source #

Similar to `fromMaybeExceptT` except the `Maybe` value is lifted in a `Monad`.

    >>> let identityNothing = Identity Nothing :: Identity (Maybe Bool)
    >>> fromMaybeMExceptT 5 identityNothing :: ExceptT Int Identity Bool
    ExceptT (Identity (Left 5))

fromMaybeOrMExceptT :: Monad m => m (Maybe a) -> x -> ExceptT x m a Source #

Just like `fromMaybeMExceptT` but with the arguments flipped.

guardExceptT :: forall (m :: Type -> Type) x. Monad m => Bool -> x -> ExceptT x m () Source #

Similar to `guard`, but for `ExceptT`.

If the `Bool` is `True`, then do nothing.

    >>> guardExceptT True "error occurred" :: ExceptT String Identity ()
    ExceptT (Identity (Right ()))

If the `Bool` is `False`, then return the error case:

    >>> guardExceptT False "error occurred" :: ExceptT String Identity ()
    ExceptT (Identity (Left "error occurred"))

guardMExceptT :: Monad m => m Bool -> x -> ExceptT x m () Source #

Just like `guardExceptT` (and similar to `guardM`), except the boolean is lifted in a `Monad`.

    >>> guardMExceptT (Identity False) "error occurred" :: ExceptT String Identity ()
    ExceptT (Identity (Left "error occurred"))

# Example converting to `ExceptT`

Functions like `fromMaybeExceptT` and `fromEitherExceptT` are convenient when paired with `collapseErrExceptT`. This section explains how you can use these functions together.

Imagine you're writing a function that pulls user names from a database, reads the first character of the name, and prints it to the console. The functions for reading names from a database, and for parsing the first character of the name could fail, so we will handle these errors by logging to the console.

Here's the function we will be using for pulling user names from the database. If we pass `0`, it returns `"SPJ"`. If we pass `1`, it returns an empty string. Otherwise it returns `Nothing`:

    >>> :{
      let getUserNameFromDb :: Int -> Maybe String
          getUserNameFromDb 0 = Just "SPJ"
          getUserNameFromDb 1 = Just ""
          getUserNameFromDb _ = Nothing
    :}

Here's the function we will be using for parsing the first character of a user name. If the user name is an empty string, we return `Left` with an error message. Otherwise we return the first character of the user name:

    >>> :{
      let parseFirstCharFromName :: String -> Either String Char
          parseFirstCharFromName [] = Left "user name is empty"
          parseFirstCharFromName (h:_) = Right h
    :}

Now let's write our function. If you didn't have the combinators from above like `fromEitherExceptT` and `collapseErrExceptT`, you might be tempted to write nested `case` patterns:

    >>> :{
      let nestedPrintFirstCharOfUserName :: Int -> IO ()
          nestedPrintFirstCharOfUserName i =
            -- Try to get the username for id i.
            case getUserNameFromDb i of
              -- If we couldn't get the user name from the database
              -- print an error to the console.
              Nothing -> putStrLn $ "ERROR: couldn't get user name for user " ++ show i
              Just name ->
                -- Try to parse the first character of the user name.
                case parseFirstCharFromName name of
                  -- If we couldn't parse the first character of the user name,
                  -- print an error to the console.
                  Left err -> putStrLn $ "ERROR: " ++ err
                  Right firstChar ->
                    -- Print the first character of the user name to the console.
                    putStrLn $
                      "Got first character of name for id " ++ show i ++ ": " ++ [firstChar]
    :}

Here's an example of using this function, including the error cases:

    >>> nestedPrintFirstCharOfUserName 100
    ERROR: couldn't get user name for user 100
    >>> nestedPrintFirstCharOfUserName 1
    ERROR: user name is empty
    >>> nestedPrintFirstCharOfUserName 0
    Got first character of name for id 0: S

This works, and is understandable, but it gets unwieldy when there are even more parsing steps. You can get very deeply nested cases.

In order to write this without deeply nested error handling, you need a short-circuiting `Monad`. Two popular examples are `MaybeT` and `ExceptT`.

Using `collapseErrExceptT` (and `ExcepT`), it is possible to write this function by short-circuiting on errors:

    >>> :{
      let printFirstCharOfUserName :: Int -> IO ()
          printFirstCharOfUserName i =
            -- The argument to collapseErrExceptT is @ExceptT (IO ()) IO ()@.
            -- This can be thought of as an action that can short-circuit
            -- with @IO ()@ error-handling actions.
            --
            -- The error-handling actions below just log to the console.
            collapseErrExceptT $ do
              -- Get the user name from the db.
              -- If getUserNameFromDb returns Nothing, then this whole
              -- block will short circuit and collapseErrExceptT will
              -- run our error handler.
              --
              -- Note that for the type of the error handler to work
              -- correctly with @collapseErrExceptT@, the error
              -- handler has to return @IO ()@.
              name <-
                fromMaybeOrExceptT (getUserNameFromDb i) $
                  putStrLn $ "ERROR: couldn't get user name for user " ++ show i
              -- Parse out the first character from the user name.
              -- If parseFirstCharFromName returns Left, then this whole
              -- block will short circuit and collapseErrExceptT will
              -- run our error handler.
              firstChar <-
                fromEitherOrExceptT (parseFirstCharFromName name) $ \err ->
                  putStrLn $ "ERROR: " ++ err
              -- Print the first character of the name.
              -- This needs to be 'lift'ed because this whole block is
              -- actually @ExceptT (IO ()) IO ()@.
              lift $
                putStrLn $
                  "Got first character of name for id " ++ show i ++ ": " ++ [firstChar]
    :}

The main good point here is that using the short-circuiting functionality of `ExceptT`, we can write everything without nesting.

Here's a few examples of calling `printFirstCharOfUserName`.

Here we pass a user id that doesn't exist, so `getUserNameFromDb` will return `Nothing`. This causes the function to short-circuit and the first error handler to be called.

    >>> printFirstCharOfUserName 100
    ERROR: couldn't get user name for user 100

Here we pass a user id that does exist, but the user name for this user id is empty. This causes the function to short-circuit and the second error handler to be called.

    >>> printFirstCharOfUserName 1
    ERROR: user name is empty

This time the function succeeds:

    >>> printFirstCharOfUserName 0
    Got first character of name for id 0: S

In real code, the functions `getUserNameFromDb` and `parseFirstCharFromName` will have monadic return values. In that case, you can use `fromMaybeOrMExceptT` and `fromEitherOrMExceptT`.

The following is setup code for doctests in this module.

    >>> import Data.Functor.Identity (Identity(Identity))