Refactoring "staircasing" with the case of `Maybe` values ​​in the" IO "code

The following function f tries to read Int twice, using the IO (Maybe Int) function twice, but performing "short circuits" after successfully reading one Int :

 readInt :: IO (Maybe Int) f :: IO (Maybe Int) f = do n1 <- readInt case n1 of Just n' -> return (Just n') Nothing -> do n2 <- readInt case n2 of Just n' -> return (Just n') Nothing -> return Nothing 

Is there a good way to reorganize this code? It would be very hairy if I extended it to three attempts ...

(My thought process: seeing this "staircasing", tells me that maybe I should use a Monad Maybe instance, but since it is already in the IO monad, I will need to use MaybeT (?) MaybeT . However, I only need one of readInt to succeed, so the behavior of the Maybe monad error on the first Nothing error would be wrong here ...)

+7
haskell monads io-monad maybe
source share
4 answers

You can use MaybeT and a MonadPlus instance to use msum :

 f :: MaybeT IO Int f = msum [readInt, readInt, readInt] 
+8
source share

You need an alternative instance for MaybeT :

 instance (Functor m, Monad m) => Alternative (MaybeT m) where empty = mzero (<|>) = mplus instance (Monad m) => MonadPlus (MaybeT m) where mzero = MaybeT (return Nothing) mplus xy = MaybeT $ do v <- runMaybeT x case v of Nothing -> runMaybeT y Just _ -> return v 

those. evaluate the first argument and return the value if it is Just , otherwise evaluate the second argument and return the value.

The code:

 import Control.Applicative import Control.Monad import Control.Monad.Trans.Maybe import Text.Read readInt :: MaybeT IO Int readInt = MaybeT $ readMaybe <$> getLine main = runMaybeT (readInt <|> readInt) >>= print 
+4
source share

First of all,

 n2 <- readInt case n2 of Just n' -> return (Just n') Nothing -> return Nothing 

really just readInt . You highlight Maybe to collect the same file.

For the rest, I think the most concise way in this case is to use the Maybe function. Then you can only get

 f = maybe readInt (return . Just) =<< readInt 
+1
source share

Another way to do this is with the iterative monad transformer from the free package.

 import Control.Monad.Trans.Iter (untilJust,retract,cutoff,IterT) readInt :: IO (Maybe Int) readInt = undefined f' :: IterT IO Int f' = untilJust readInt f :: IO (Maybe Int) f = (retract . cutoff 2) f' 

Where cutoff indicates the maximum number of attempts.

The advantage of this approach is that you can easily alternate other repetitive actions with f' , thanks to the MonadPlus IterT . For example, registration actions or wait actions.

+1
source share

All Articles