这样的功能已经存在吗? (或者,这个函数有什么更好的名字?)

发布于 2024-12-06 04:05:10 字数 452 浏览 1 评论 0原文

我最近多次使用以下模式编写了代码,并且想知道是否有更短的方法来编写它。

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 有什么更好的名字?

I've written code with the following pattern several times recently, and was wondering if there was a shorter way to write it.

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

To make things a little cleaner, I wrote this function (though I'm not sure it's an appropriate name):

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

I can then make foo like this:

foo = getLine >>= constM putStrLn

Does a function/idiom like this already exist? And if not, what's a better name for my constM?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

丶视觉 2024-12-13 04:05:10

好吧,让我们考虑一下如何简化这样的事情。我猜非单子版本看起来像 const' fa = const a (fa) ,这显然相当于具有更具体类型的 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 = (>>) <
gt; f <*> g

...或者,如果您愿意:

doBoth :: (Monad m) => (a -> m b) -> (a -> m c) -> a -> m c
doBoth = liftA2 (>>)

当然,liftA2 相当于 liftM2 ,所以你可能想知道将 monad 上的操作提升到另一个 monad 是否与 monad 转换器有关;一般来说,那里的关系很尴尬,但在这种情况下它很容易工作,给出这样的东西:

doBoth :: (Monad m) => ReaderT a m b -> ReaderT a m c -> ReaderT a m c
doBoth = (>>)

......当然,模适当的包装等等。为了专门返回到您的原始版本,return 的原始用法现在需要是 ReaderT am a 类型,这应该不难识别为 <阅读器 monad 的 code>ask 函数。


另一个角度是认识到像 (Monad m) => 这样类型的函数。一个-> m b 可以直接组合,就像纯函数。函数 (<=<) :: Monad m =>; (b→mc)→ (a→mb)→ (a -> mc) 给出与函数组合 (.) :: (b -> c) ->; 直接等价的函数。 (a→b)→ (a -> c),或者您也可以使用 Control.Categorynewtype 包装器 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

因为我们不关心结果第一个 Kleisli 箭头,只有它的副作用,我们可以以明显的方式丢弃元组的一半:

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,因此如果您概括类型签名。然而,扩展纯函数的定义并简化如下可能会有所启发:

  • \fx -> (f &&& id >>> arr snd) x
  • \fx -> (snd . (\y -> (fy, id y))) x
  • \fx -> (\y -> snd (fy, y)) x
  • \fx -> (\y -> y) x
  • \fx -> x。

所以我们回到了 flip const,正如预期的那样!


简而言之,您的函数是 (>>)flip const 的一些变体,但在某种程度上依赖于差异 - 前者同时使用ReaderT 环境和底层 monad 的 (>>),后者使用特定 Arrow 和期望箭头副作用以特定顺序发生。由于这些细节,不可能进行任何概括或简化。从某种意义上说,您使用的定义与需要的一样简单,这就是为什么我给出的替代定义更长和/或涉及一定量的包装和展开。

像这样的函数将是某种“monad 实用程序库”的自然补充。虽然 Control.Monad 提供了一些类似的组合器,但它远非详尽无遗,而且我在标准库中既找不到也无法回忆起此函数的任何变化。然而,如果我在 hackage 上的一个或多个实用程序库中找到它,我一点也不感到惊讶。

在基本上放弃了存在问题之后,除了您可以从上面有关相关概念的讨论中获得的内容之外,我实际上无法提供太多有关命名的指导。

最后,还要注意,您的函数没有基于一元表达式结果的控制流选择,因为无论主要目标是什么,都会执行表达式。拥有独立于参数内容的计算结构(即 Monad m => m a 中的 a 类型的内容)通常表明您实际上并不需要一个完整的 Monad,并且可以使用更通用的 Applicative 概念。

Well, let's consider the ways that something like this could be simplified. A non-monadic version would I guess look something like const' f a = const a (f a), which is clearly equivalent to flip const with a more specific type. With the monadic version, however, the result of f a can do arbitrary things to the non-parametric structure of the functor (i.e., what are often called "side effects"), including things that depend on the value of a. What this tells us is that, despite pretending like we're discarding the result of f a, we're actually doing nothing of the sort. Returning a unchanged as the parametric part of the functor is far less essential, and we could replace return with something else and still have a conceptually similar function.

So the first thing we can conclude is that it can be seen as a special case of a function like the following:

doBoth :: (Monad m) => (a -> m b) -> (a -> m c) -> a -> m c
doBoth f g a = f a >> g a

From here, there are two different ways to look for an underlying structure of some sort.


One perspective is to recognize the pattern of splitting a single argument among multiple functions, then recombining the results. This is the concept embodied by the Applicative/Monad instances for functions, like so:

doBoth :: (Monad m) => (a -> m b) -> (a -> m c) -> a -> m c
doBoth f g = (>>) <
gt; f <*> g

...or, if you prefer:

doBoth :: (Monad m) => (a -> m b) -> (a -> m c) -> a -> m c
doBoth = liftA2 (>>)

Of course, liftA2 is equivalent to liftM2 so you might wonder if lifting an operation on monads into another monad has something to do with monad transformers; in general the relationship there is awkward, but in this case it works easily, giving something like this:

doBoth :: (Monad m) => ReaderT a m b -> ReaderT a m c -> ReaderT a m c
doBoth = (>>)

...modulo appropriate wrapping and such, of course. To specialize back to your original version, the original use of return now needs to be something with type ReaderT a m a, which shouldn't be too hard to recognize as the ask function for reader monads.


The other perspective is to recognize that functions with types like (Monad m) => a -> m b can be composed directly, much like pure functions. The function (<=<) :: Monad m => (b -> m c) -> (a -> m b) -> (a -> m c) gives a direct equivalent to function composition (.) :: (b -> c) -> (a -> b) -> (a -> c), or you can instead make use of Control.Category and the newtype wrapper Kleisli to work with the same thing in a generic way.

We still need to split the argument, however, so what we really need here is a "branching" composition, which Category alone doesn't have; by using Control.Arrow as well we get (&&&), letting us rewrite the function as follows:

doBoth :: (Monad m) => Kleisli m a b -> Kleisli m a c -> Kleisli m a (b, c)
doBoth f g = f &&& g

Since we don't care about the result of the first Kleisli arrow, only its side effects, we can discard that half of the tuple in the obvious way:

doBoth :: (Monad m) => Kleisli m a b -> Kleisli m a c -> Kleisli m a c
doBoth f g = f &&& g >>> arr snd

Which gets us back to the generic form. Specializing to your original, the return now becomes simply id:

constKleisli :: (Monad m) => Kleisli m a b -> Kleisli m a a
constKleisli f = f &&& id >>> arr snd

Since regular functions are also Arrows, the definition above works there as well if you generalize the type signature. However, it may be enlightening to expand the definition that results for pure functions and simplify as follows:

  • \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.

So we're back to flip const, as expected!


In short, your function is some variation on either (>>) or flip const, but in a way that relies on the differences--the former using both a ReaderT environment and the (>>) of the underlying monad, the latter using the implicit side-effects of the specific Arrow and the expectation that Arrow side effects happen in a particular order. Because of these details, there's not likely to be any generalization or simplification available. In some sense, the definition you're using is exactly as simple as it needs to be, which is why the alternate definitions I gave are longer and/or involve some amount of wrapping and unwrapping.

A function like this would be a natural addition to a "monad utility library" of some sort. While Control.Monad provides some combinators along those lines, it's far from exhaustive, and I could neither find nor recall any variation on this function in the standard libraries. I would not be at all surprised to find it in one or more utility libraries on hackage, however.

Having mostly dispensed with the question of existence, I can't really offer much guidance on naming beyond what you can take from the discussion above about related concepts.

As a final aside, note also that your function has no control flow choices based on the result of a monadic expression, since executing the expressions no matter what is the main goal. Having a computational structure independent of the parametric content (i.e., the stuff of type a in Monad m => m a) is usually a sign that you don't actually need a full Monad, and could get by with the more general notion of Applicative.

腻橙味 2024-12-13 04:05:10

嗯,我认为 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

它别无选择,只能忽略它的第一个参数。所以这个功能纯粹没什么好说的。

我将其视为一种观察具有副作用值的方法。 observeeffectsideEffect 可能是不错的名字。 observe 是我的最爱,但也许只是因为它朗朗上口,而不是因为它清晰可​​见。

Hmm, I don't think constM is appropriate here.

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

const :: b -> a -> b

So perhaps:

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

The function you are M-ing seems to be:

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

Which has no choice but to ignore its first argument. So this function does not have much to say purely.

I see it as a way to, hmm, observe a value with a side effect. observe, effect, sideEffect may be decent names. observe is my favorite, but maybe just because it is catchy, not because it is clear.

一抹微笑 2024-12-13 04:05:10

我真的不知道这完全存在,但是你在解析器生成器中经常看到这种情况,只有不同的名称(例如,将 东西放在括号内) - 通常是某种运算符(例如 fparsec 中的 >>..>>)。要真正给出一个名字,我可能会称之为忽略

I don't really have a clue this exactly allready exists, but you see this a lot in parser-generators only with different names (for example to get the thing inside brackets) - there it's normaly some kind of operator (>>. and .>> in fparsec for example) for this. To really give a name I would maybe call it ignore?

吾性傲以野 2024-12-13 04:05:10

交互

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

这不完全是你的意思要求,但它是相似的。

There's interact:

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

It's not quite what you're asking for, but it's similar.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文