使用Maybe a,IO a和MaybeT IO a

时间:2012-12-06 19:53:42

标签: haskell monad-transformers maybe

我正在编写一个快速响应式系统,其中包含一系列Maybe a,IO a和MaybeT IO a的各种组合,并且有很多东西需要考虑。有些IO动作没有无效输入(因此没有包含在MaybeT中),有些是(并返回一个MaybeT IO a),有些不是IO动作但可能会失败,所以返回Maybe a,and some这只是简单的价值观,而且我开始认为我必须记住<$>, Just, fmap, MaybeT, lift, =<<,return的过度组合,只是为了让一切都成为正确的类型。有没有更简单的方法来管理它或推断我需要使用哪些函数来获取我需要它们的值?或者我只是希望我随着时间的推移变得更好?这是我的例子:

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:"

我必须说,我对缺乏简洁感到失望,即使我删除了类型提示,我可能会编写一个较短的函数来在C#或python中执行相同的操作

2 个答案:

答案 0 :(得分:18)

由于您只提供了一个代码片段,我无法尝试重构它。但是,这就是我要做的:大多数monad都有相应的类型。它的原因正是你需要的:当你使用monad变换器创建一个monad时,它将继承内部monad的操作(如果适用)。所以你可以忘记内部的monad并在最终的monad中工作。

在您的情况下,您有MaybeT IO。它是MonadPlusMonadIO的实例。因此,您可以重构返回Maybe something的代码以使用常规MonadPlus实例,只需将Just替换为return,将Nothing替换为mzero }。像:

-- 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

它适用于任何MonadPlus,包括MaybeMaybeT IO

您可以重构返回IO something的代码以使用常规MonadIO实例:

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

这样,您可以忘记<$> / fmap / liftMJustMaybeT等。您只需使用returnmzero以及某些地方liftIO

这也可以帮助您创建更通用的代码。如果您稍后意识到需要向monad堆栈添加内容,则只要新的monad堆栈实现相同的类型类,现有代码就不会中断。

答案 1 :(得分:0)

我不那么雄心勃勃的回答。查看代码,getPiece等操作实际上并未从特定错误站点返回任何信息。如果你真的想要那些,你可以放弃使用IO并将异常转换为Maybe值。我将一些示例代码与代码中引用的一些未定义函数放在一起:

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)

值得注意的是,我已从MaybeT IO Piece移至IO (Maybe Piece)。我没有使用fmaplift,而是使用do表示法来引用我的IO操作的中间结果。

继续你对C#或Python的评论,我希望这是你正在寻找的那种更简单的答案。