【问题标题】:Pure maps in a monadmonad 中的纯映射
【发布时间】:2020-01-21 21:03:59
【问题描述】:

假设我有两个函数

f :: Monad m => a -> m a
g :: a -> a

我想像这样连续应用于某些元素:

(return x) >>= f >>= g

这不起作用,因为 g 是纯的,所以我首先需要“人为地”将它变成一元的。一种可能是

(return x) >>= f >>= (return . g)

这对我来说不是很直观。另一种可能性是使用 Monad is Applicative:

(return g) <*> ((return x) >>= f)

但这不是很直观,因为函数和参数的顺序不同:

(>>=) :: Monad m => m a -> (a -> m b) -> m b
(<*>) :: Applicative f => f (a -> b) -> f a -> f b

处理此问题的规范方法是什么?如果(&gt;&gt;==) = (flip ((&lt;*&gt;) . pure)) 可以写((pure x) &gt;&gt;= f) &gt;&gt;== g,除了运算符优先级之外,这很好。当然,monadic 代码中的纯函数是很常见的事情,那么肯定有一种标准的方法来处理它们吗?

编辑:我最初并没有这么说,但我在想一种情况,我有几个函数,一些是纯函数,一些是单子函数,我想以某种随机顺序应用它们。

【问题讨论】:

  • 你可以使用fmap,所以return x &gt;&gt;= fmap g . f,你可以将其简化为fmap g (f x)

标签: haskell monads


【解决方案1】:

你在这里描述的是fmap :: Functor f =&gt; (a -&gt; b) -&gt; f a -&gt; f b。此外,由于pure x &gt;&gt;= f 应该与f x 相同,因此我们可以将给定的表达式简化为:

<b>fmap</b> g (f x)

或者我们可以使用中缀别名(&lt;$&gt;) :: Functor f =&gt; (a -&gt; b) -&gt; f a -&gt; f b

g <b>&lt;$&gt;</b> f x

【讨论】:

  • 谢谢!这是缩短&lt;*&gt; . return,对吗?但是,如果我有几个这些交织在一起怎么办。假设我想申请 f 然后 g 然后 f 然后 g?
  • @StefanWitzel:是的。您可以像(g . f . g . f) &lt;$&gt; x 一样“链接”左侧的功能,但也许我误解了您的问题?
  • 不,我不能(至少不是那么容易)因为 f 的输出是一元的,所以 g . f 没有意义。
  • @StefanWitzel:你可以导入它:import Control.Monad((&gt;=&gt;))
  • (g &lt;$&gt; f) 是一种晦涩难懂的拼写方式 (g . f)
【解决方案2】:

我认为最直接的解决方案是来自Control.Monad 模块的Kleisli 组合&gt;=&gt;

由于&gt;=&gt;(.) 都是右关联的并且(.) 具有更高的优先级,因此您可以编写以下内容:

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

f :: Monad m => a -> m a
g :: a -> a
h :: Monad m => a -> m b
j :: b -> b

q :: Monad m => a -> m b
q  =  f >=> return . g >=> h >=> return . j
--       |         |- Use (return .) to transform (a -> b) into (a -> m b)
--       |- Use Kleisli composition >=> to compose Kleisli arrows, i.e.
--       |-   the functions going from values to monadic values, (a -> m b)

【讨论】:

  • 函数a -&gt; m a是Kleisli 箭头&gt;=&gt;&lt;=&lt; 是 Kleisli(从左到右和从右到左)composition 运算符。
  • @WillNess 感谢您的赞誉。不知道;)
  • OP 已经拒绝简单地使用 return . g 作为“不直观”,但我同意这是一种干净的方法。直到通过重复使用变得直观时,某些东西才会变得不直观:)
  • @chepner:是的,并且具有正确的优先级会有所帮助(这使您的回答很有帮助)。感谢 Ismor 和 WillNess。这一切都非常有启发性!
【解决方案3】:

您可以为您的 (&gt;&gt;==) 运算符提供适当的优先级以允许删除括号

(>>==) = flip ((<*>) . pure)
infixl 1 >>==

然后

> (>>==) = flip ((<*>) . pure); infixl 1 >>==
> pure 3 >>= (\x -> [x, x]) >>== (+1)
[4,4]

【讨论】:

    【解决方案4】:

    感谢@WillemVanOnsems 指向 Control.Monad 的指针,我找到了一个解决方案,这与我一直在寻找的精神是使用 =&lt;&lt;,它与 &lt;*&gt;&lt;$&gt; 具有相同的参数顺序:

    g <$> (f =<< f =<< g <$> g <$> (f =<< y))
    

    现在y 已经是一元的了(比如return x)。我唯一不满意的是由于运算符优先级而导致的括号。

    【讨论】:

    • 我发现类型图here 很有帮助;你也可以。还有底部的“四种应用”here
    猜你喜欢
    • 2012-01-12
    • 1970-01-01
    • 1970-01-01
    • 2016-03-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-26
    • 1970-01-01
    相关资源
    最近更新 更多