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

时间:2011-09-22 16:58:05

标签: haskell monads idioms

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

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

4 个答案:

答案 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

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

我认为这是一种方法,嗯,观察带有副作用的值observeeffectsideEffect可能是不错的名字。 observe是我最喜欢的,但也许只是因为它很吸引人,而不是因为它很清楚。

答案 2 :(得分:1)

我真的不知道这究竟是否已经存在,但你在解析器生成器中看到了很多但只有不同的名称(例如在括号内得到 thing ) - 它就是为此,请使用某种运算符(例如fparsec中的>>..>>)。要真正给出一个名字,我可以称之为忽略

答案 3 :(得分:0)

interact

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

这不是你所要求的,但它是相似的。