Хорошо, давайте рассмотрим, как можно упростить что-то подобное . Немонадическая версия, я думаю, будет выглядеть примерно как 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 = (>>)
... по модулю подходящей упаковки и тому подобное, конечно. Чтобы вернуться к исходной версии, исходное использование 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
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
побочные эффекты происходят в определенном порядке. Из-за этих деталей вряд ли будет доступно какое-либо обобщение или упрощение. В некотором смысле, используемое вами определение является настолько простым, насколько это необходимо, поэтому альтернативные определения, которые я дал, длиннее и / или включают некоторую степень переноса и разворачивания.
Такая функция была бы естественным дополнением к некоторой «библиотеке утилит монады». Хотя Control.Monad
предоставляет некоторые комбинаторы в том же духе, это далеко не исчерпывающий характер, и я не смог найти или вспомнить какой-либо вариант этой функции в стандартных библиотеках. Однако я не удивлюсь, если найду его в одной или нескольких служебных библиотеках.
После того, как я по большей части обошелся без вопросов о существовании, я не могу предложить много рекомендаций по наименованию, помимо того, что вы можете извлечь из приведенного выше обсуждения связанных понятий.
В заключение отметим также, что ваша функция не имеет выбора потока управления на основе результата монадического выражения, поскольку выполнение выражений независимо от того, что является главной целью. Наличие вычислительной структуры, независимой от параметрического содержимого (т. Е. Материал типа a
в Monad m => m a
), обычно является признаком того, что вам на самом деле не нужен полный Monad
, и вы могли бы обойтись с более общим понятием Applicative
.