Существует ли такая функция? (Или, как лучше назвать эту функцию?)

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

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 = getLine >>= constM putStrLn

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

4 ответов


Ну, давайте рассмотрим способы, которые что-то вроде этого мог бы быть упрощена. Не-монадическая версия, я думаю, будет выглядеть примерно так: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) дает прямой эквивалент функции composition (.) :: (b -> c) -> (a -> b) -> (a -> c), или вы можете вместо этого использовать Control.Category и newtype фантик Kleisli для работы с тем же самым в общем виде.

нам все еще нужно разделить аргумент, однако, так что нам действительно нужно здесь "ветвящаяся" композиция, которая не имеет; с помощью 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

С регулярной функции Arrows, определение выше также работает там, если вы обобщаете подпись типа. Однако может быть полезно расширить определение, которое приводит к чистым функциям, и упростить его следующим образом:

  • \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, as ожидал!


короче говоря, ваша функция является некоторым изменением либо (>>) или flip const, но таким образом, который опирается на различия-первый использует оба ReaderT окружающая среда и (>>) лежащей в основе монады, последняя использует неявные побочные эффекты конкретного Arrow и ожидание, что Arrow побочные эффекты происходят в определенном порядке. Из-за этих деталей вряд ли будет доступно какое-либо обобщение или упрощение. В в некотором смысле определение, которое вы используете, так же просто, как и должно быть, поэтому альтернативные определения, которые я дал, длиннее и/или включают некоторое количество упаковки и разворачивания.

такая функция была бы естественным дополнением к какой-то" библиотеке утилит монады". В то время как Control.Monad предоставляет некоторые комбинаторы по этим линиям, это далеко не исчерпывающий, и я не мог ни найти, ни вспомнить какие-либо изменения этой функции в стандартных библиотеках. Я бы не стал однако совсем не удивлен, найдя его в одной или нескольких служебных библиотеках на hackage.

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

в заключение обратите внимание, что ваша функция не имеет выбора потока управления, основанного на результате монадического выражения, так как выполнение выражений независимо от основной цели. Имея вычислительная структура, независимая от параметрического содержания (т. е. материал типа a на Monad m => m a) обычно является признаком того, что вам на самом деле не нужен полный Monad, и мог бы обойтись более общим понятием Applicative.


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


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


здесь interact:

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

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