【问题标题】:Working with Maybe a, IO a, and MaybeT IO a使用 Maybe a、IO a 和 MaybeT IO a
【发布时间】:2012-12-06 19:53:41
【问题描述】:

我正在编写一个快速响应式系统,其中包含了 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 中做同样的事情

【问题讨论】:

  • 使用 lift 并尝试在不使用构造函数的情况下编写所有单子代码(这将解决您的冗长问题,并使重构更容易)

标签: haskell monad-transformers maybe


【解决方案1】:

由于您只提供了一个代码片段,我无法尝试对其进行重构。但是,这就是我要做的:大多数 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

这样,您可以忘记&lt;$&gt;/fmap/liftMJustMaybeT 等。您只需使用returnmzero 和在某些地方liftIO

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

【讨论】:

    【解决方案2】:

    我的一个不那么雄心勃勃的答案。查看您的代码,您的操作(如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,我希望这是您正在寻找的那种更简单的答案。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-31
      • 1970-01-01
      • 2022-01-23
      • 2016-03-31
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多