Рефакторинг "лестницы" в случае значений "Может" в коде "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, Регистрация действий, например, или ожидание действий.

Другие вопросы по тегам