4

我正在编写一个快速响应式系统,其中包含了 Maybe a、IO a 和 MaybeT IO a 的各种组合,并且有很多东西需要考虑。一些没有无效输入的 IO 操作(因此不包含在 MaybeT 中),一些是(并返回 MaybeT IO a)一些不是 IO 操作但可能失败,因此返回 Maybe a,还有一些这只是简单的值,并且开始似乎我必须记住过度组合<$>, 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 中做同样的事情

4

2 回答 2

18

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

在你的情况下,你有MaybeT IO. MonadPlus它是和的实例MonadIO。因此,您可以重构返回Maybe something以使用通用MonadPlus实例的代码,只需替换JustwithreturnNothingwith即可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等。您只需使用,和在某些地方。liftMJustMaybeTreturnmzeroliftIO

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

于 2012-12-06T20:57:27.063 回答
0

我的一个不那么雄心勃勃的答案。查看您的代码,您的操作getPiece不会真正从特定错误站点返回任何信息。Maybe如果您真的想要这些,您可能只需使用 IO 并将异常转换为值即可。我将一些示例代码与您的代码中引用的一些未定义函数放在一起:

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). 而不是使用fmaporlift我只是使用do符号来引用我的IO操作的中间结果。

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

于 2012-12-06T23:49:37.420 回答