使用 IO 代码中的 Maybe 值重构“阶梯”
Refactoring “staircasing” with case of `Maybe` values in `IO` code
以下函数 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
有什么好的方法可以重构这段代码吗?如果我将它扩展到三次尝试,这将变得非常毛茸茸......
(我的思考过程:看到这个“阶梯”告诉我也许我应该使用 Maybe
的 Monad
实例,但因为它已经在 IO
monad 中,然后我将不得不使用 MaybeT
(?)。但是,我只需要 readInt
中的 one 就可以成功,所以 Maybe
monad 的在第一个 Nothing
上出错的行为在这里是错误的...)
您可以使用 MaybeT
和 MonadPlus
实例来使用 msum
:
f :: MaybeT IO Int
f = msum [readInt, readInt, readInt]
首先,
n2 <- readInt
case n2 of
Just n' -> return (Just n')
Nothing -> return Nothing
实际上只是 readInt
。您正在挑选一个 Maybe
值,以便将相同的值放在一起。
对于其余部分,我认为在这种情况下最简洁的方法是使用 maybe
函数。那么你就可以获得
f = maybe readInt (return . Just) =<< readInt
您需要 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
,则计算第一个参数和 return 值,否则计算第二个参数和 return 值。
代码:
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
另一种方法是使用 free
包中的 iterative monad transformer。
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
指定最大重试次数。
这种方法的优点是,由于 IterT
的 MonadPlus
实例,您可以轻松地 interleave other repeated actions 和 f'
。例如记录操作或等待操作。
以下函数 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
有什么好的方法可以重构这段代码吗?如果我将它扩展到三次尝试,这将变得非常毛茸茸......
(我的思考过程:看到这个“阶梯”告诉我也许我应该使用 Maybe
的 Monad
实例,但因为它已经在 IO
monad 中,然后我将不得不使用 MaybeT
(?)。但是,我只需要 readInt
中的 one 就可以成功,所以 Maybe
monad 的在第一个 Nothing
上出错的行为在这里是错误的...)
您可以使用 MaybeT
和 MonadPlus
实例来使用 msum
:
f :: MaybeT IO Int
f = msum [readInt, readInt, readInt]
首先,
n2 <- readInt
case n2 of
Just n' -> return (Just n')
Nothing -> return Nothing
实际上只是 readInt
。您正在挑选一个 Maybe
值,以便将相同的值放在一起。
对于其余部分,我认为在这种情况下最简洁的方法是使用 maybe
函数。那么你就可以获得
f = maybe readInt (return . Just) =<< readInt
您需要 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
,则计算第一个参数和 return 值,否则计算第二个参数和 return 值。
代码:
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
另一种方法是使用 free
包中的 iterative monad transformer。
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
指定最大重试次数。
这种方法的优点是,由于 IterT
的 MonadPlus
实例,您可以轻松地 interleave other repeated actions 和 f'
。例如记录操作或等待操作。