-
Notifications
You must be signed in to change notification settings - Fork 6
Description
tl;dr: I suggest exposing the functions
runBail :: Except a a -> a
runBail = runIdentity . runBailT
runBailT :: ExceptT a m a -> m a
runBailT act = either id id <$> runExceptT act
bail :: a -> ExceptT a m x
bail = throwEfrom Control.Monad.Except, or from some new module.
One of the things I use ExceptT for is adding an early-return to avoid adding indent levels. Something like
lookupDomain :: URL -> IO (Either NoSuchDomain IpAddr)
lookupDomain url = fmap (either id id) $ runExceptT $ do
check1 <- lookupInLocalCache url
maybe (pure ()) throwE check1
check2 <- lookupInHostFile url
maybe (pure ()) throwE check2
check3 <- lookupThroughDNS url
maybe (pure ()) throwE check3
pure $ Left NoSuchDomainin place of
lookupDomain :: URL -> IO (Either NoSuchDomain IpAddr)
lookupDomain url = do
check1 <- lookupInLocalCache url
case check1 of
Just (eResult) -> pure eResult
Nothing -> do
check2 <- lookupInHostFile url
case check2 of
Just (eResult) -> pure eResult
Nothing -> do
check3 <- lookupThroughDNS url
case check3 of
Just (eResult) -> pure eResult
Nothing -> pure $ Left NoSuchDomainIt's fine, but doesn't feel quite fit for purpose. In particular using throwE for something that isn't an error feels a bit off. I'll often add a comment saying "using throwE as an early-return" or similar, to make it clear what's going on.
Could there be something specifically intended for early-return?
One possibility would be to expose the functions
runBail :: Except a a -> a
runBail = runIdentity . runBailT
runBailT :: ExceptT a m a -> m a
runBailT act = either id id <$> runExceptT act
bail :: a -> ExceptT a m x
bail = throwEfrom Control.Monad.Except, or from somewhere else. Then the first example doesn't change much:
lookupDomain :: URL -> IO (Either NoSuchDomain IpAddr)
lookupDomain url = runBailT $ do
check1 <- lookupInLocalCache url
maybe (pure ()) bail check1
check2 <- lookupInHostFile url
maybe (pure ()) bail check2
check3 <- lookupThroughDNS url
maybe (pure ()) bail check3
pure $ Left NoSuchDomainBut IMO it's a bit clearer. And when newcomers ask "how do I return early from a function", there are dedicated names we can point them at.
The awkward things here are that there's no corresponding BailT transformer, and that it's still possible to use throwE and (worse) catchE. Still, on balance I think I'd like this. It's not a big deal but I think it would be a slight improvement.
It would also be possible to have a BailT transformer, but it would be exactly the same as ExceptT. You can't remove the separate type parameter for the error, because e.g. you need maybe (pure ()) bail check1 :: ExceptT e m (). You could have type BailT m a = ExceptT a m a, but then it can't be used everywhere. And of course, an extra transformer means a bunch more instances to write. So I like this less.
Maybe there's something else that could work here?