Можно ли изменить тип монады в одной монаде последовательности?
Я знаю, что можно изменить завернутый тип, так что вы можете иметь
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