Такая функция уже существует? (Или как лучше назвать эту функцию?) - PullRequest
13 голосов
/ 22 сентября 2011

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

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?

Ответы [ 4 ]

18 голосов
/ 22 сентября 2011

Хорошо, давайте рассмотрим, как можно упростить что-то подобное . Немонадическая версия, я думаю, будет выглядеть примерно как 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.

3 голосов
/ 22 сентября 2011

Хм, я не думаю, что 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, кажется,:

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

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

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

1 голос
/ 22 сентября 2011

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

0 голосов
/ 22 сентября 2011

Есть interact:

http://hackage.haskell.org/packages/archive/haskell98/latest/doc/html/Prelude.html#v:interact

Это не совсем то, что вы просите, но похоже.

...