Для начала предлагаю не обобщать m
до MonadReader
. В типе flip
...
flip :: (a -> b -> c) -> (b -> a -> c)
... средняя стрелка не похожа на другие: она просто связывает вход и выход щелчка. Принимая это упрощение, мы получаем более простой тип для genericFlip
, который вы предлагаете:
genericFlip :: (MonadReader a n, MonadReader b o) => o (n c) -> n (o c)
В любом случае, genericFlip
не может быть реализован с этой подписью. Сам по себе интерфейс MonadReader
не предоставляет способ предоставить среду для вычислений, которая была бы необходима для обмена слоями. Возьмем, к примеру, специализированную genericFlip
из вашего вопроса:
genericFlip' :: (MonadReader a n, MonadReader b o) => (b -> a -> c) -> n (o c)
genericFlip' f = do
a <- ask
return $ do
b <- ask
return $ f b a
Она основывается на том, что f
является функцией, что означает, что мы можем предоставить ей среду (и, как вы заметили, мы использовали Reader
вместо этого мы могли бы сделать то же самое через runReader
). В конечном итоге все, что MonadReader
делает здесь, это превращает функции в вычисления для читателей, что становится прозрачным благодаря этому бессмысленному написанию:
genericFlip' :: (MonadReader a n, MonadReader b o) => (b -> a -> c) -> n (o c)
genericFlip' = fmap reader . reader . flip
Одно обобщение flip
, которое у нас есть, это distribute
:
distribute :: (Distributive g, Functor f) => f (g a) -> g (f a)
distribute @((->) _)
также известен как flap
или (??)
, тогда как distribute @((->) _) @((->) _)
равно flip
.
Distributive
, тем не менее, не вполне отвлекает нас от функций в том смысле, в котором мы могли бы надеяться в контексте этого вопроса. Каждый дистрибутивный функтор изоморфен от c до (->) r
для некоторого специфика c r
. Связи становятся более очевидными, когда мы смотрим на класс Representable
, который в принципе эквивалентен Distributive
, но использует более сложную кодировку, которая делает изоморфизм явным. В дополнение к distribute
, являющемуся обобщением flip
, мы имеем index
в качестве аналога приложения-функции и tabulate
, который очень похож на reader
. Класс, по сути, предлагает реализацию по умолчанию MonadReader
, которую можно легко получить с помощью Co
newtype .
В заключение отметим, что что-то, что, несмотря на то, что мы не совсем точно подходим к нашим предлагаемым обобщенным перевернутым сигнатурам, очень легко переворачивается, это ReaderT r (ReaderT s m) a
, которое сводится к r -> s -> m a
. Возможно, это не так уж и полезно, хотя на практике, как правило, вместо того, чтобы иметь вложенные слои считывателей, объединить среды в один тип и иметь только один слой считывателей (см. Также RIO
monad * 1064). *).