这样的功能已经存在吗? (或者,这个函数有什么更好的名字?)
我最近多次使用以下模式编写了代码,并且想知道是否有更短的方法来编写它。
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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
好吧,让我们考虑一下如何简化这样的事情。我猜非单子版本看起来像 const' fa = const a (fa) ,这显然相当于具有更具体类型的 Flip const 。然而,对于一元版本,
f a
的结果可以对函子的非参数结构执行任意操作(即通常称为“副作用”的操作),包括依赖于a
的值。这告诉我们的是,尽管假装好像我们正在丢弃f a
的结果,但实际上我们什么也没做。将a
不变地返回作为仿函数的参数部分远没有那么重要,我们可以用其他东西替换return
并且仍然具有概念上相似的函数。因此,我们可以得出的第一件事是,它可以被视为函数的特殊情况,如下所示:
从这里开始,有两种不同的方法来查找某种底层结构。
一种观点是认识到在多个函数之间拆分单个参数,然后重新组合结果的模式。这是函数的
Applicative
/Monad
实例体现的概念,如下所示:...或者,如果您愿意:
当然,
liftA2
相当于 liftM2 ,所以你可能想知道将 monad 上的操作提升到另一个 monad 是否与 monad 转换器有关;一般来说,那里的关系很尴尬,但在这种情况下它很容易工作,给出这样的东西:......当然,模适当的包装等等。为了专门返回到您的原始版本,
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.Category
和newtype
包装器Kleisli
来工作以通用的方式使用相同的东西。然而,我们仍然需要拆分参数,因此我们真正需要的是“分支”组合,而
Category
本身并不具备这种功能;通过使用Control.Arrow
以及我们得到(&&&)
,让我们重写函数如下:因为我们不关心结果第一个 Kleisli 箭头,只有它的副作用,我们可以以明显的方式丢弃元组的一半:
这让我们回到通用形式。专门针对您的原始版本,
return
现在变成简单的id
:由于常规函数也是
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 toflip const
with a more specific type. With the monadic version, however, the result off 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 ofa
. What this tells us is that, despite pretending like we're discarding the result off a
, we're actually doing nothing of the sort. Returninga
unchanged as the parametric part of the functor is far less essential, and we could replacereturn
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:
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:...or, if you prefer:
Of course,
liftA2
is equivalent toliftM2
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:...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 typeReaderT a m a
, which shouldn't be too hard to recognize as theask
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 ofControl.Category
and thenewtype
wrapperKleisli
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 usingControl.Arrow
as well we get(&&&)
, letting us rewrite the function as follows: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:
Which gets us back to the generic form. Specializing to your original, the
return
now becomes simplyid
:Since regular functions are also
Arrow
s, 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
(>>)
orflip const
, but in a way that relies on the differences--the former using both aReaderT
environment and the(>>)
of the underlying monad, the latter using the implicit side-effects of the specificArrow
and the expectation thatArrow
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
inMonad m => m a
) is usually a sign that you don't actually need a fullMonad
, and could get by with the more general notion ofApplicative
.嗯,我认为 constM 在这里不合适。
所以也许:
您正在
M
-ing的函数似乎是:它别无选择,只能忽略它的第一个参数。所以这个功能纯粹没什么好说的。
我将其视为一种观察具有副作用值的方法。
observe
、effect
、sideEffect
可能是不错的名字。observe
是我的最爱,但也许只是因为它朗朗上口,而不是因为它清晰可见。Hmm, I don't think
constM
is appropriate here.So perhaps:
The function you are
M
-ing seems to be: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.我真的不知道这完全存在,但是你在解析器生成器中经常看到这种情况,只有不同的名称(例如,将 东西放在括号内) - 通常是某种运算符(例如 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?有
交互
: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.