Ответ 1
Итак, рассмотрим способы упрощения такого рода. Не-монадическая версия, я думаю, выглядит примерно как const' f a = const a (f a)
, что явно эквивалентно flip const
с более конкретным типом. Однако с монадической версией результат f a
может делать произвольные вещи в непараметрической структуре функтора (т.е. Часто называемые "побочные эффекты" ), включая вещи, которые зависят от значения a
, Это говорит нам о том, что, несмотря на то, что мы делали вид, будто мы отбрасываем результат f a
, мы фактически ничего не делаем. Возвращая a
без изменений, поскольку параметрическая часть функтора гораздо менее существенна, и мы могли бы заменить return
чем-то другим и все еще иметь концептуально подобную функцию.
Итак, первое, что мы можем заключить, состоит в том, что его можно рассматривать как частный случай такой функции, как:
doBoth :: (Monad m) => (a -> m b) -> (a -> m c) -> a -> m c
doBoth f g a = f a >> g a
Здесь есть два разных способа поиска какой-либо базовой структуры.
Одной из перспектив является распознавание шаблона , разделяющего один аргумент среди нескольких функций, а затем рекомбинирование результатов. Это концепция, воплощенная экземплярами Applicative
/Monad
для функций, например:
doBoth :: (Monad m) => (a -> m b) -> (a -> m c) -> a -> m c
doBoth f g = (>>) <$> f <*> g
... или, если вы предпочитаете:
doBoth :: (Monad m) => (a -> m b) -> (a -> m c) -> a -> m c
doBoth = liftA2 (>>)
Конечно, liftA2
эквивалентно liftM2
, поэтому вам может возникнуть вопрос, связано ли какое-либо действие с монадами в монаде, если операция на монадах в другую монаду; в общем, отношения там неудобны, но в этом случае он работает легко, давая что-то вроде этого:
doBoth :: (Monad m) => ReaderT a m b -> ReaderT a m c -> ReaderT a m c
doBoth = (>>)
... modulo подходящая упаковка и, конечно же, такая. Чтобы специализироваться на исходной версии, первоначальное использование return
теперь должно быть чем-то с типом ReaderT a m a
, которое не должно быть слишком сложно распознать как функцию ask
для монадов читателей.
Другая перспектива заключается в том, чтобы распознавать, что функции с такими типами, как (Monad m) => a -> m b
, могут быть составлены напрямую, подобно чистым функциям. Функция (<=<) :: Monad m => (b -> m c) -> (a -> m b) -> (a -> m c)
дает прямой эквивалент композиции функции (.) :: (b -> c) -> (a -> b) -> (a -> c)
, или вы можете использовать Control.Category
и newtype
wrapper Kleisli
для работы с одним и тем же общим способом.
Нам все же нужно разделить аргумент, поэтому нам действительно нужен "ветвящийся" состав, которого нет только Category
; используя Control.Arrow
, получим (&&&)
, позволяя нам переписать функцию следующим образом:
doBoth :: (Monad m) => Kleisli m a b -> Kleisli m a c -> Kleisli m a (b, c)
doBoth f g = f &&& g
Поскольку нас не волнует результат первой стрелки Клейсли, только ее побочные эффекты, мы можем отбросить эту половину кортежа очевидным образом:
doBoth :: (Monad m) => Kleisli m a b -> Kleisli m a c -> Kleisli m a c
doBoth f g = f &&& g >>> arr snd
Что возвращает нас к общей форме. Специализируясь на вашем оригинале, return
теперь становится просто id
:
constKleisli :: (Monad m) => Kleisli m a b -> Kleisli m a a
constKleisli f = f &&& id >>> arr snd
Так как регулярные функции также Arrow
s, то вышеописанное определение работает, если вы обобщите подпись типа. Тем не менее, может быть полезно расширить определение, которое приводит к чистым функциям, и упростить следующее:
-
\f x -> (f &&& id >>> arr snd) x
-
\f x -> (snd . (\y -> (f y, id y))) x
-
\f x -> (\y -> snd (f y, y)) x
-
\f x -> (\y -> y) x
-
\f x -> x
.
Итак, мы вернулись к flip const
, как и ожидалось!
Короче говоря, ваша функция представляет собой некоторую вариацию на (>>)
или flip const
, но в том, что зависит от различий - первая использует как среду ReaderT
, так и (>>)
основной монады, последний использует неявные побочные эффекты конкретного Arrow
и ожидание того, что побочные эффекты Arrow
происходят в определенном порядке. Из-за этих подробностей вряд ли будет доступно обобщение или упрощение. В некотором смысле определение, которое вы используете, точно так же просто, как и должно быть, поэтому альтернативные определения, которые я дал, длиннее и/или включают некоторое количество обертывания и разворачивания.
Функция, подобная этой, была бы естественным дополнением к "библиотеке утилиты monad". Хотя Control.Monad
предоставляет некоторые комбинаторы вдоль этих строк, это далеко не исчерпывающий, и я не мог ни найти, ни вспомнить какие-либо изменения этой функции в стандартных библиотеках. Однако я не был бы удивлен, если бы нашел его в одной или нескольких библиотеках утилиты для взлома.
В основном отказавшись от вопроса о существовании, я не могу действительно предлагать много указаний на именование, кроме того, что вы можете извлечь из обсуждения выше о связанных понятиях.
В качестве окончательного аспекта обратите внимание также, что ваша функция не имеет вариантов управления потоком, основанных на результате монадического выражения, поскольку выполняет выражения независимо от того, что является главной целью. Наличие вычислительной структуры, не зависящей от параметрического содержимого (т.е. Материала типа a
в Monad m => m a
), обычно является признаком того, что вам действительно не нужен полный Monad
, и он может пройти с более общим понятием Applicative
.