Подтвердить что ты не робот

Существует ли такая функция уже существует? (Или, какое лучшее имя для этой функции?)

Я недавно написал код со следующим шаблоном и задавался вопросом, существует ли более короткий способ его записи.

foo :: IO String
foo = do
    x <- getLine
    putStrLn x >> return x

Чтобы сделать вещи немного чище, я написал эту функцию (хотя я не уверен, что это подходящее имя):

constM :: (Monad m) => (a -> m b) -> a -> m a
constM f a = f a >> return a

Затем я могу сделать foo следующим образом:

foo = getLine >>= constM putStrLn

Существует ли уже существующая функция/идиома? А если нет, то какое лучшее имя для моего constM?

4b9b3361

Ответ 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.

Ответ 2

Хм, я не думаю, что constM здесь уместен.

map :: (a -> b) -> [a] -> [b]
mapM :: (Monad m) => (a -> m b) -> [a] -> m b

const :: b -> a -> b

Итак, возможно:

constM :: (Monad m) => b -> m a -> m b
constM b m = m >> return b

Функция, которой вы являетесь M -ing, выглядит следующим образом:

f :: (a -> b) -> a -> a

У кого нет выбора, кроме как игнорировать его первый аргумент. Так что этой функции нечего сказать чисто.

Я рассматриваю это как способ, хм, наблюдать значение с побочным эффектом. observe, effect, sideEffect могут быть достойными именами. observe - мой любимый, но, возможно, просто потому, что он запоминающийся, а не потому, что это понятно.

Ответ 3

На самом деле я не знаю, что это точно существует, но вы видите это в генераторах парсеров только с разными именами (например, чтобы получить предмет внутри скобок) - там нормализуется какой-то оператор (>>. и .>> в fparsec, например) для этого. Чтобы действительно назвать имя, я бы назвал его игнорировать?