Working with Maybe a, IO a and MaybeT IO a

I am writing a quick response style system with a bunch of different combinations of Maybe a, IO a and MaybeT IO a, and there are a lot of things to consider. Some I / O operations for which there is no invalid input (and therefore they are not wrapped in MaybeT), some of them (and return MaybeT IO a), some of which are not IO actions, but may fail, so return Maybe a and some which are just equal values ​​and start to seem that I should remember the exorbitant combinations <$>, Just, fmap, MaybeT, lift, =<<, and return only so that everything is correct. Is there an easier way to handle this, or reason about which functions I need to use to get my values ​​where I need them? Or do I just need to hope that I will get better with time? Here is my example:

 getPiece :: Player -> Board -> MaybeT IO Piece getPiece player@ (Player pieces _ _ _) board = piece where promptString = displayToUserForPlayer player board ++ "\n" ++ (display player) ++ "\n" ++ "Enter piece number: " input :: MaybeT IO String input = lift $ prompt promptString index :: MaybeT IO Int index = MaybeT <$> return <$> ((fmap cvtFrom1indexedInt) . maybeRead) =<< input piece :: MaybeT IO Piece piece = MaybeT <$> return <$> maybeIndex pieces =<< index getRotatedPiece :: Player -> Board -> MaybeT IO Piece getRotatedPiece player@ (Player pieces _ _ _) board = piece where promptString :: MaybeT IO String promptString = (++) <$> displayListString <*> restOfString input :: MaybeT IO String input = MaybeT <$> (fmap Just) <$> prompt =<< promptString index :: MaybeT IO Int index = MaybeT <$> return <$> ((fmap cvtFrom1indexedInt) . maybeRead) =<< input piece :: MaybeT IO Piece piece = MaybeT <$> return <$> maybeIndex pieces =<< index rotatedPieceList :: MaybeT IO [Piece] rotatedPieceList = rotations <$> getPiece player board displayListString :: MaybeT IO String displayListString = displayNumberedList <$> rotatedPieceList restOfString :: MaybeT IO String restOfString = MaybeT <$> return <$> Just $ "\nEnter rotation number:" 

I have to say that I am disappointed by the lack of brevity, even if I removed the type hints that I could write a shorter function to do the same in C # or python

+4
source share
2 answers

Since you provided only a snippet of code, I cannot reorganize it. However, this is what I would do: most monads have an appropriate class type. The reason for this is exactly what you need here: when you create a monad using a monad transformer, it inherits the operations of the internal monads (if necessary). Thus, you can forget about the internal monads and work only in the final monad.

In your case, you have MaybeT IO . This is a copy of MonadPlus and MonadIO . This way you can reorganize the code that Maybe something returns to work with a common MonadPlus instance instead, just replace Just with return and Nothing with mzero . How:

 -- before checkNumber :: Int -> Maybe Int checkNumber x | x > 0 = Just x | otherwise = Nothing x -- after checkNumber :: MonadPlus m => Int -> m Int checkNumber x | x > 0 = return x | otherwise = mzero -- or just: checkNumber = mfilter (> 0) . return 

It will work with any MonadPlus , including Maybe and MaybeT IO .

And you can reorganize the code that returns IO something to work with a common MonadIO instance:

 -- before doSomeIO :: IO () doSomeIO = getLine >>= putStrLn -- after doSomeIO :: MonadIO m => m () doSomeIO = liftIO $ getLine >>= putStrLn 

So you can forget about <$> / fmap / liftM , Just , MaybeT , etc. You just use return , mzero and in some places liftIO .

It will also help you create more general code. If you later realize that you need to add something to the monad stack, existing code will not break if the new monad stack implements the same type classes.

+18
source

Less ambitious answer from me. Looking at your code, your operations, such as getPiece , do not really return any information from a specific error site. You might just be able to use IO and turn exceptions into Maybe values ​​if you really want to. Some sample code that I put along with some undefined functions specified in your code:

 import Control.Exception (handle, IOException) data Board = Board deriving (Show) data Piece = Piece deriving (Show) type Pieces = [Piece] data Player = Player Pieces () () () deriving (Show) prompt :: String -> IO String prompt = undefined cvtFrom1indexedInt :: Int -> Int cvtFrom1indexedInt = undefined maybeIndex :: Pieces -> Int -> Maybe Piece maybeIndex = undefined displayToUserForPlayer :: Player -> Board -> String displayToUserForPlayer = undefined display :: Player -> String display = undefined -- I used this when testing, to deal with the Prelude.undefined errors --returnSilently :: SomeException -> IO (Maybe a) returnSilently :: IOException -> IO (Maybe a) returnSilently e = return Nothing getPiece :: Player -> Board -> IO (Maybe Piece) getPiece player@ (Player pieces _ _ _) board = handle returnSilently $ do let promptString = displayToUserForPlayer player board ++ "\n" ++ (display player) ++ "\n" ++ "Enter piece number: " input <- prompt promptString let index = cvtFrom1indexedInt (read input) return (maybeIndex pieces index) main = do maybePiece <- getPiece (Player [] () () ()) Board putStrLn ("Got piece: " ++ show maybePiece) 

In particular, I moved from MaybeT IO Piece to IO (Maybe Piece) . Instead of using fmap or lift I just used the notation do to refer to the intermediate results of my IO action.

Continuing your comments about C # or Python, I hope this was the simpler answer you were looking for.

0
source

All Articles