7

以下函数f尝试Int通过使用IO (Maybe Int)函数两次读取 an ,但在成功读取 one 后“短路”执行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

有没有重构这段代码的好方法?如果我将其扩展到 3 次尝试,这将变得非常棘手……</p>

(我的思考过程:看到这个“楼梯”告诉我也许我应该使用 的Monad实例Maybe,但是由于这已经在IOmonad 中,所以我必须使用MaybeT(?)。但是,我只需要一个readIntto成功,所以Maybe单子第一次Nothing出错的行为在这里是错误的......)

4

4 回答 4

8

您可以使用MaybeTMonadPlus要使用的实例msum

f :: MaybeT IO Int
f = msum [readInt, readInt, readInt]
于 2015-01-24T11:22:05.343 回答
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
于 2015-01-24T11:28:10.523 回答
1

首先,

n2 <- readInt
case n2 of
      Just n' -> return (Just n')
      Nothing -> return Nothing

真的只是readInt。您正在挑选一个Maybe值以便将相同的值放在一起。

其余的,我认为在这种情况下最简洁的方法是使用maybe函数。然后你可以得到

f = maybe readInt (return . Just) =<< readInt
于 2015-01-24T11:22:28.470 回答
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指定最大重试次数。

这种方法的优点是您可以轻松地将其他重复操作与交错f'这要归功于MonadPlus. IterT例如,记录操作或等待操作。

于 2015-01-24T12:12:53.730 回答