我正在编写一个快速响应式系统,其中包含一系列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中执行相同的操作
答案 0 :(得分:18)
由于您只提供了一个代码片段,我无法尝试重构它。但是,这就是我要做的:大多数monad都有相应的类型。它的原因正是你需要的:当你使用monad变换器创建一个monad时,它将继承内部monad的操作(如果适用)。所以你可以忘记内部的monad并在最终的monad中工作。
在您的情况下,您有MaybeT IO
。它是MonadPlus
和MonadIO
的实例。因此,您可以重构返回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
,包括Maybe
和MaybeT IO
。
您可以重构返回IO something
的代码以使用常规MonadIO
实例:
-- before
doSomeIO :: IO ()
doSomeIO = getLine >>= putStrLn
-- after
doSomeIO :: MonadIO m => m ()
doSomeIO = liftIO $ getLine >>= putStrLn
这样,您可以忘记<$>
/ fmap
/ liftM
,Just
,MaybeT
等。您只需使用return
, mzero
以及某些地方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)
。我没有使用fmap
或lift
,而是使用do
表示法来引用我的IO
操作的中间结果。
继续你对C#或Python的评论,我希望这是你正在寻找的那种更简单的答案。