Можно ли изменить тип монады в одной монаде последовательности?

Я знаю, что можно изменить завернутый тип, так что вы можете иметь

f :: (a -> m b)
g :: (b -> m c)
f >>= g :: (a -> m c)

но можно ли изменить m? Если m это MonadError и реализуется как Either ErrorA и Either ErrorB, Я могу как-то связать их? Очевидно, я не могу связать их напрямую, потому что какой будет тип Left? Тем не менее, я в ситуации, когда я в конечном итоге звоню show в любом случае, но я не нашел лучшего решения, чем

case mightFail1 of
  Left e -> show e
  Right v -> either show doStuff mightFail2

, который не правильно используйте монадическое поведение остановки при первой ошибке без необходимости проверять явно.

4 ответов


это невозможно сделать в монадической цепи.

обратите внимание, что это на самом деле не связано с Monad вообще: вы никоим образом не связываете вложенное монадическое действие в Left аргумент или что-то в этом роде, но вы только преобразуете сам аргумент. Это в основном операция функтора fmap, но слева вместо правой части:

fmap     :: (r->ρ) -> Either l r -> Either l ρ
fmapLeft :: (l->λ) -> Either l r -> Either λ r

функция с этой конкретной сигнатурой, удивительно,не похоже на exist. Однако эта идея функтора с двумя ковариантными аргументами, очевидно, более общая, чем просто Either, и вообще есть специальный класс. Он имеет (ИМО довольно неудачное название, столкновения с Arrow)

Data.Bifunctor.first :: (a -> b) -> p a c -> p b c

, который специализируется на том, чтобы

first :: (a -> b) -> Either a c -> Either b c

так что вы можете использовать

f :: (Show a) => (Either a b) -> (Either String b)
f = first show

все понятие " изменение контейнера "называется"естественным преобразованием". В частности, нам нужна функция, которая преобразует контейнеры, не влияя на то, что внутри. Мы можем гарантировать, что это так в системе типов, используя forall.

-- natural transformation
type (m :~> n) = forall a. m a -> n a

тогда они могут быть применены, когда мы хотим. Например, если вы можете преобразовать ErrorA -> ErrorB тогда есть общая операция для вас

mapE :: (e -> e') -> (Either e :~> Either e')
mapE f (Left e)  = Left (f e)
mapE _ (Right a) = a

вы даже можете получить действительно фантазии с операторами типа и суммы типы.

-- a generic sum type
infixr 9 :+:
newtype (a :+: b) = Inl a | Inr b

liftE :: (Either e :~> Either (e' :+: e))
liftE = mapE Inr

Бифункторы достигают примерно такого же эффекта, но это совершенно другой способ просмотра проблемы. Вместо того чтобы обычно изменять контейнер, они влияют на другой ковариантный параметр (или индекс) в самом контейнере. Таким образом, бифункторные действия всегда можно рассматривать как естественные преобразования, но НТС более общие.


для данного конкретного случая вы можете использовать fmapL от меня errors библиотека:

fmapL :: (a -> b) -> Either a r -> Either b r

так как вы сказали, что собираетесь show оба они, в конце концов, вы можете использовать fmapL show объединить их обоих, чтобы договориться о Either String монада и последовательность их напрямую:

do v <- fmapL show mightFail1
   fmapL show $ mightFail2 v

теперь вы можете упорядочить оба из них с помощью нотации и иметь их общий механизм обработки ошибок.

отметим, что show это не единственный способ объединить левых ценности. Вы также можете унифицировать не отображаемые значения с помощью ... Either!

example :: Either (Either Error1 Error2) ()
example = do
    v <- fmapL Left mightFail1
    fmapL Right $ mightFail2 v

StackOverflow-отличная резиновая утка. Я нашел a решение, но мне все еще интересно, есть ли другой подход. Поскольку я уже закончил писать вопрос, я все равно отправлю его.

вместо того, чтобы думать о "изменении типа монады во время цепи", просто преобразуйте все значения, прежде чем они будут прикованы, заставляя их возвращаться Either String a:

f :: (Show a) => (Either a b) -> (Either String b)
f = either (Left . show) (Right)

это обертывает весь вызов (f mightFail1), может быть составной вариант (f . mightFail1)

сумасшедший режим: оберните либо в newtype, сделайте его экземпляром функтора, который отображает функцию с левой стороны, а не с правой стороны, и просто вызовите fmap show mightFail1 (не забудьте обернуть и развернуть свой newtype). это имеет смысл? : D