我正在编写一个即时响应风格的系统,其中包含一堆可能是a、IO a和MaybeT IO a的不同组合,并且有许多东西需要考虑。有些IO操作没有无效的输入(因此没有包装在MaybeT中),有些(并返回MaybeT IO a)有些不是IO操作,但可能会失败,所以可能会返回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编写一个更短的函数来做同样的事情
发布于 2012-12-07 04:57:27
因为您只提供了代码片段,所以我不能尝试重构它。然而,这就是我要做的:大多数monad都有一个对应的type类。它的原因正是您在这里需要的:当您使用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堆栈实现相同的类型类,现有的代码就不会中断。
发布于 2012-12-07 07:49:37
我给出了一个不那么雄心勃勃的答案。看一下您的代码,像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的评论,我希望这是你想要的那种更简单的答案。
https://stackoverflow.com/questions/13751430
复制相似问题