使用`IO`代码中的`Maybe`值重构“阶梯状结构”问题

7
以下函数f尝试通过两次使用IO (Maybe Int)函数来读取Int,但在成功读取一个Int后“短路”执行:
readInt :: IO (Maybe Int)

f :: IO (Maybe Int)
f = do
  n1 <- readInt
  case n1 of
      Just n' -> return (Just n')
      Nothing -> do
        n2 <- readInt
        case n2 of
              Just n' -> return (Just n')
              Nothing -> return Nothing

有没有一种好的方法来重构这段代码?如果我将其扩展到三次尝试,这将变得非常棘手...
(我的思考过程:看到这个“阶梯状”,让我想到也许我应该使用MaybeMonad实例,但由于这已经在IO单子中,因此我接下来需要使用MaybeT(?)。然而,我只需要一个readInt成功,因此Maybe单子的错误行为在第一个Nothing上失败这点是不正确的...)
4个回答

8
您可以使用MaybeTMonadPlus实例来使用msum:
f :: MaybeT IO Int
f = msum [readInt, readInt, readInt]

或许为了其他人的利益,可以澄清一下他需要将 IO (Maybe Int) 计算包装在 MaybeT 构造器中。 - Gabriella Gonzalez

4
你需要为 MaybeT 准备替代实例:
instance (Functor m, Monad m) => Alternative (MaybeT m) where
    empty = mzero
    (<|>) = mplus

instance (Monad m) => MonadPlus (MaybeT m) where
    mzero = MaybeT (return Nothing)
    mplus x y = MaybeT $ do
        v <- runMaybeT x
        case v of
            Nothing -> runMaybeT y
            Just _  -> return v

即计算第一个参数并返回该值(如果它为Just),否则计算第二个参数并返回该值。

代码:

import Control.Applicative
import Control.Monad
import Control.Monad.Trans.Maybe
import Text.Read

readInt :: MaybeT IO Int
readInt = MaybeT $ readMaybe <$> getLine 

main = runMaybeT (readInt <|> readInt) >>= print

1
另一种方法是使用来自free包的迭代单子变换器
import Control.Monad.Trans.Iter (untilJust,retract,cutoff,IterT)

readInt :: IO (Maybe Int)
readInt = undefined

f' :: IterT IO Int
f' = untilJust readInt

f :: IO (Maybe Int)
f = (retract . cutoff 2) f'

cutoff指定最大重试次数。

这种方法的优点是,由于IterTMonadPlus实例,您可以轻松地交错其他重复操作f'。例如记录操作或等待操作。


1
首先,
n2 <- readInt
case n2 of
      Just n' -> return (Just n')
      Nothing -> return Nothing

实际上只是readInt。您正在拆分Maybe值,以便组合相同的值。

对于其余部分,我认为在这种情况下最简洁的方法是使用maybe函数。然后您可以仅获取

f = maybe readInt (return . Just) =<< readInt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接