这样的功能是不是已经存在? (或者,这个函数有啥更好的名字?)
Posted
技术标签:
【中文标题】这样的功能是不是已经存在? (或者,这个函数有啥更好的名字?)【英文标题】:Does a function like this already exist? (Or, what's a better name for this function?)这样的功能是否已经存在? (或者,这个函数有什么更好的名字?) 【发布时间】:2011-11-23 00:46:52 【问题描述】:我最近多次使用以下模式编写代码,想知道是否有更短的方法来编写它。
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 有什么更好的名字?
【问题讨论】:
看来你可以使用普通函子来定义这个函数:constF :: Functor f => (a -> f b) -> a -> f a; constF f a = a <$ f a
@FUZxxl,这也能正常工作,谢谢......那么这是对 const 名称的正确用法吗?
@FUZxxl,我不太确定我是否理解在 FP 中如何命名事物。只是想确保这个名字没有误导。它看起来有点像 const,只是我在我提供的函数中使用了 'a',这就是我不确定的原因。
我猜这个名字不错。 PS:您可以使定义更短:constF = ap (<$)
比您可以内联它:foo = getLine >>= (<$) <*> putStrLn
【参考方案1】:
有interact
:
http://hackage.haskell.org/packages/archive/haskell98/latest/doc/html/Prelude.html#v:interact
这不是你想要的,但它是相似的。
【讨论】:
他也想返回结果;interact
不能这样做。我想,他只是想通过一个简单的例子来解释他想要的功能。你的回答不是很有帮助。【参考方案2】:
我真的不知道这完全存在,但你在解析器生成器中看到了很多,只有不同的名称(例如,将 thing 放在括号内) - 就在那里通常为此使用某种运算符(例如,fparsec 中的>>.
和.>>
)。要真正给出一个名字,我可能会称之为ignore?
【讨论】:
我见过有人用ignore
这个名字来代替ignore :: Monad m => m a -> m ()
、ignore m = m >> return ()
。
@Judah 隐藏有关丢弃 do 语句结果的警告?
我不确定忽略是否正确,因为我需要返回给定的值。
@FUZxxl 这是一种用途;另一个是像forkIO
这样期望()
返回值的函数。
是的 - 这就是我把 ? 放在那里的原因 - 我认为 ignore 因为它基本上忽略了 f
正在做什么(除了它的副作用当然 ;) ) ... 但是你没问题,Judah 给出的版本肯定更适合被称为 ignore - `sideeffectsOnly` 呢? :D【参考方案3】:
好吧,让我们考虑一下可以简化这种事情的方式。我猜一个非单子版本看起来像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
,所以你可能想知道是否将 monad 上的操作提升到另一个 monad 是否与 monad 转换器有关;一般来说,那里的关系很尴尬,但在这种情况下它很容易工作,给出如下内容:
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
由于我们不关心第一个 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
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
环境和底层 monad 的 (>>)
,后者使用特定 Arrow
的隐式副作用以及 Arrow
副作用以特定顺序发生的期望。由于这些细节,不可能有任何概括或简化。从某种意义上说,您使用的定义完全符合它的需要,这就是为什么我给出的替代定义更长和/或涉及一些包装和展开。
这样的函数自然会添加到某种“monad 实用程序库”中。虽然Control.Monad
提供了一些符合这些方面的组合器,但它远非详尽无遗,而且我在标准库中既找不到也无法回忆起这个函数的任何变化。但是,如果在一个或多个关于 hackage 的实用程序库中找到它,我一点也不感到惊讶。
在很大程度上省略了存在的问题,除了你可以从上面关于相关概念的讨论中得到的东西之外,我真的不能提供太多关于命名的指导。
最后,请注意,您的函数没有基于一元表达式结果的控制流选择,因为无论主要目标是什么,都会执行表达式。拥有一个独立于参数内容的计算结构(即Monad m => m a
中的a
类型的东西)通常表明您实际上不需要完整的Monad
,并且可以使用更一般的概念Applicative
.
【讨论】:
不是他的问题的答案。无论如何+1以获得出色的分析。 :-) @luqui: 好吧,它的目的是对寻找底层结构以找到某事物的概括的一种迂回解释,因为 exact 函数描述不存在......但是,是的,那种在途中迷路了。可能应该在某个时候稍微修改一下以使其更清晰。【参考方案4】:嗯,我觉得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
是我最喜欢的,但也许只是因为它好听,而不是因为它清晰。
【讨论】:
我认为 observe 比我的名字更清晰,而且我猜它不需要一个“M”,因为与 this 完全等效是没有任何意义的。 我怀疑observe
。我们当然不会观察函数产生的值,而且由于函数只需要Applicative
,我们也不一定会观察到任何副作用。以上是关于这样的功能是不是已经存在? (或者,这个函数有啥更好的名字?)的主要内容,如果未能解决你的问题,请参考以下文章