Fun with Eithers

A couple fun tricks I just found with Either/ExceptT:

If you want a function to return a result type that has multiple modes of failure/success, and you don’t want to separate them like

data Failure = FailA | FailB
data Success = SuccessA Int | SuccessB String
myFunc :: String -> Either Failure Success
myFunc s = do
  when (null s) $ Left FailA
  x <- thing s :: Either Failure Int
  pure $ if x > 0 then SuccessA x else SuccessB s

You could use Either a a to abort early with the final result, or end with the final result:

data Result = FailA | FailB | SuccessA Int | SuccessB String
myFunc :: String -> Result
myFunc s = either id id $ do
  when (null s) $ Left FailA
  x <- thing s :: Either Result Int
  pure $ if x > 0 then SuccessA x else SuccessB s

And if you want to have early abort within just a single function to make it easier to read, just use Either a Void:

myFunc :: String -> Int
myFunc s = either id absurd $ do
  s' <-
    case s of
      "zero" -> returnE 0
      '+' : s' -> pure s'
     _ -> pure $ otherFunc s

  returnE $ length s' * 2
  where
    returnE = Left
6 Likes

I like this too. It’s the idea behind Bluefin’s EarlyReturn.

I think this warrants its own name. I proposed this interface:

newtype EarlyT r m a = EarlyT (m (Either r a)) deriving Functor

instance Monad m => Applicative (EarlyT r m) where ...
instance Monad m => Monad (EarlyT r m) where ...
instance MonadTrans (EarlyT r) where ...

earlyReturn :: Applicative m => r -> EarlyT r m a
earlyReturn = EarlyT . pure . Left

runEarlyT :: Monad m => EarlyT a m a -> m a
runEarlyT (EarlyT m) = m >>= either pure pure

A bit about running Either-returning IO actions concurrently.

If you have several actions of the form IO (Either e a), you want run all the IO actions concurrently to completion (even if some of them return with Left) and afterwards combine the results with the usual Applicative instance of Either e, you can use this Applicative:

import Data.Functor.Compose (Compose (..))
import Control.Concurrent.Async

type ConcurrentlyE' e a = Compose Concurrently (Either e) a 

Using Compose from base and Concurrently from async. Applicative composition runs in phases: first all the effects of the outer Applicative (here, Concurrently) and later those of the inner one (Either e).

But what if we wanted to promptly cancel all pending actions the moment one of the actions returns with Left? Perhaps we aren’t interested in completing the remaining effects in case of failure.

We can do that with the special-purpose ConcurrentlyE type from async. There’s also the convenience concurrentlyE function:

concurrentlyE :: IO (Either e a) -> IO (Either e b) -> IO (Either e (a, b))
1 Like