Рефакторинг "лестницы" в случае значений "Может" в коде "IO"
Следующая функция f
пытается прочитать Int
дважды с помощью IO (Maybe 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
Есть ли хороший способ рефакторинга этого кода? Это было бы очень волосатым, если бы я продлил его до трех попыток...
(Мой мыслительный процесс: видя эту "лестницу" говорит мне, что, возможно, я должен использовать Monad
экземпляр Maybe
, но так как это уже в IO
монада, я бы тогда использовал MaybeT
(?). Однако мне нужен только один из readInt
чтобы добиться успеха, поэтому Maybe
Поведение монады из-за ошибки первого Nothing
было бы неправильно здесь...)
4 ответа
Ты можешь использовать MaybeT
и MonadPlus
экземпляр для использования msum
:
f :: MaybeT IO Int
f = msum [readInt, readInt, 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
, иначе вычислите второй аргумент и верните значение.
Код:
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
Прежде всего,
n2 <- readInt
case n2 of
Just n' -> return (Just n')
Nothing -> return Nothing
действительно просто readInt
, Вы разделяете Maybe
значение для того, чтобы собрать один и тот же.
В остальном, я думаю, самый краткий способ в этом случае - это использовать maybe
функция. Тогда вы можете получить только
f = maybe readInt (return . Just) =<< readInt
Еще один способ сделать это с помощью итеративного монадного преобразователя из 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
, Регистрация действий, например, или ожидание действий.