Какой-то беспорядок с обертыванием/развертыванием/связыванием монад, связанный с контейнером, содержащим Maybe

Вот немного примера кода

foo :: a -> Identity (Maybe a)
foo a = do
  maybeStuff <- getStuffSometimes a
  return $ case maybeStuff of                  -- this "case" stuff is the kind
    Just stuff -> Just $ getStuffAlways stuff  -- of code I'd expect the Maybe
    Nothing -> Nothing                         -- monad to help with

getStuffSometimes :: a -> Identity (Maybe a)
getStuffSometimes a = return $ Just a

getStuffAlways :: a -> Identity a
getStuffAlways = return

-- ERROR (on the return statement of the do block)
-- Expected type: Identity (Maybe a)
--   Actual type: Identity (Maybe (Identity a))

Может быть немного неясно, что я пытаюсь сделать, поэтому вот более подробное описание:

  • Все самое интересное заключено в вычислительный контекст/контейнер — в данном случае Identity для демонстрации. В моем реальном коде у меня, конечно, есть еще одна монада.

  • Предполагается, что foo и getStuffSometimes принимают что-то типа a и возвращают Maybe a, завернутый в контекст, то есть они возвращают либо Identity (Just a) (вычисление выполнено успешно, либо Identity Nothing (вычисление не выполнено).

  • getStuffAlways — это вычисление, которое никогда не дает сбоев — оно всегда возвращает Identity a.

  • Я хочу, чтобы foo:

    1. Run a failable computation
    2. Если неудавшееся вычисление завершается ошибкой, завершается ошибкой (без ничего)
    3. Если ошибочное вычисление завершилось успешно, свяжите его с помощью getStuffAlways и верните Just (result of getStuffAlways to the result of #1)

Является ли это вариантом использования Monad Transformers? В моем случае моя фактическая монада немного сложна и представляет собой стек нескольких преобразователей, предоставленных моей библиотекой поверх IO, и я не совсем уверен, как заставить ее работать в этой ситуации (я бы задал ее в другом вопросе в итоге приходится использовать трансформаторы)


Следовать за:

В реальной жизни у меня есть что-то вроде:

foo :: a -> Identity (a, Maybe a)
foo a = do
  firstPart <- getStuffAlways a

  maybeStuff <- getStuffSometimes a
  secondPart <- case maybeStuff of
    Just stuff -> Just $ getStuffAlways stuff
    Nothing -> Nothing

  return (firstPart, secondPart)

Как лучше построить стек в этом случае?


person Justin L.    schedule 02.08.2013    source источник


Ответы (1)


Да, это вариант использования монадных преобразователей. Учитывая вычисление типа:

computation :: (Monad m) => m (Maybe a)

... вы можете обернуть его в MaybeT, используя одноименный конструктор MaybeT:

MaybeT :: (Monad m) => m (Maybe a) -> MaybeT m a

MaybeT computation :: (Monad m) => MaybeT m a

Тогда вы можете просто написать:

foo :: MaybeT Identity a
foo = do
   stuff <- MaybeT getStuffSometimes
   lift $ getStuffAlways stuff

... и MaybeT позаботится обо всех проверках Nothing за вас, убедившись, что они проходят через другую вашу монаду.

Когда закончите, просто используйте runMaybeT, чтобы развернуть результат, возвращая действие в базовой монаде, которое возвращает Maybe:

runMaybeT :: (Monad m) => MaybeT m a -> m (Maybe a)

Например:

runMaybeT foo :: Identity (Maybe a)

Изменить: чтобы ответить на ваш дополнительный вопрос, вы просто должны сделать:

foo a = do
    firstPart <- getStuffAlways
    secondPart <- runMaybeT $ do
        stuff <- MaybeT getStuffSometimes a
        lift $ getStuffAlways stuff
    return (firstPart, secondPart)
person Gabriel Gonzalez    schedule 02.08.2013
comment
Спасибо, вроде неплохо :) У меня дополнительный вопрос. Что, если foo :: a -> Identity (a,Maybe a) ? То есть первая часть кортежа — это вычисление, которое не может потерпеть неудачу, и snd из него таковым является. Как бы я структурировал это? Мне завернуть все foo в MaybeT? Или пусть только getStuffSometimes будет MaybeT, а foo позвонит runMaybeT? - person Justin L.; 03.08.2013
comment
На самом деле я столкнулся с некоторыми ошибками, пытаясь реализовать это с конкретными типами (т.е. String вместо a): я получаю сообщение об ошибке Coudn't match expected type Maybe a0 with actual type String; ожидаемое Identity (Maybe a0), фактическое Identity String в возвращаемом типе getStuffAlways во второй строке foo. - person Justin L.; 03.08.2013
comment
Ничего, получается, что второй MaybeT должен быть lift, и теперь все работает :) Со своим продолжением я тоже разобрался. На самом деле не заметил, что вы ответили на него, но большое спасибо! - person Justin L.; 03.08.2013