我最近多次用以下模式编写代码,并且想知道是否有更短的编写方式。
foo :: IO String
foo = do
x <- getLine
putStrLn x >> return x
为了让事情变得更清洁,我写了这个函数(虽然我不确定它是否合适):</ p>
constM :: (Monad m) => (a -> m b) -> a -> m a
constM f a = f a >> return a
然后我就可以这样做了:
foo = getLine >>= constM putStrLn
这样的功能/习语是否已经存在?如果没有,我的constM有什么更好的名字?
答案 0 :(得分:18)
好吧,让我们考虑一下像这样的东西可以的简化方法。我猜一个非monadic版本看起来像const' f a = const a (f a)
,这明显等同于具有更具体类型的flip const
。但是,对于monadic版本,f a
的结果可以对仿函数的非参数结构(即通常称为“副作用”)执行任意操作,包括依赖于{的值的事物。 {1}}。这告诉我们的是,尽管假装就像我们放弃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
所以你可能想知道是否将monad上的操作提升到另一个monad与monad变换器有关;一般来说,这种关系很尴尬,但在这种情况下它很容易运作,给出这样的东西:
liftM2
当然, ...模块化适当的包装等。要专门回到原始版本,doBoth :: (Monad m) => ReaderT a m b -> ReaderT a m c -> ReaderT a m c
doBoth = (>>)
的原始用法现在需要是return
类型的内容,这不应该太难识别为读者monads的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
,让我们按如下方式重写函数:
(&&&)
由于我们不关心第一个Kleisli箭头的结果,只有它的副作用,我们可以用显而易见的方式丢弃那一半的元组:
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
s,如果你概括了类型签名,那么上面的定义也适用于那里。但是,扩展导致纯函数的定义并简化如下可能是有启发性的:
Arrow
\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
环境和{底层monad的{1}},后者使用特定ReaderT
的隐含副作用和期望(>>)
副作用以特定顺序发生。由于这些细节,不太可能有任何概括或简化。从某种意义上说,你使用的定义就像它需要的那样简单,这就是我给出的替代定义更长和/或涉及一些包装和展开的原因。
这样的函数将成为某种“monad实用程序库”的自然补充。虽然Arrow
沿着这些线提供了一些组合器,但它远非详尽无遗,我无法在标准库中找到或回忆起此函数的任何变化。但是,在hackage的一个或多个实用程序库中找到它并不会感到惊讶。
大部分时间都没有存在的问题,除了上面关于相关概念的讨论之外,我无法提供更多关于命名的指导。
作为最后的一点,请注意您的函数没有基于monadic表达式的结果的控制流选择,因为执行表达式无论主要目标是什么。具有独立于参数内容的计算结构(即Arrow
中的Control.Monad
类型的东西)通常表明您实际上不需要完整的a
,并且可以获得通过Monad m => m a
的更一般概念。
答案 1 :(得分:3)
嗯,我认为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
是我最喜欢的,但也许只是因为它很吸引人,而不是因为它很清楚。
答案 2 :(得分:1)
我真的不知道这究竟是否已经存在,但你在解析器生成器中看到了很多但只有不同的名称(例如在括号内得到 thing ) - 它就是为此,请使用某种运算符(例如fparsec中的>>.
和.>>
)。要真正给出一个名字,我可以称之为忽略?
答案 3 :(得分:0)
有interact
:
http://hackage.haskell.org/packages/archive/haskell98/latest/doc/html/Prelude.html#v:interact
这不是你所要求的,但它是相似的。